no message

This commit is contained in:
Peter Iakovlev
2018-03-27 12:03:39 +04:00
parent d67854a097
commit 33cc8132be
17 changed files with 1393 additions and 825 deletions

View File

@@ -362,16 +362,15 @@
D08F4A671E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08F4A651E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift */; };
D08F4A691E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */; };
D08F4A6A1E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */; };
D093D7EB206413C900BC3599 /* SecureIdPassportIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7EA206413C900BC3599 /* SecureIdPassportIdentity.swift */; };
D093D7EC206413C900BC3599 /* SecureIdPassportIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7EA206413C900BC3599 /* SecureIdPassportIdentity.swift */; };
D093D7EB206413C900BC3599 /* SecureIdIdentityValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7EA206413C900BC3599 /* SecureIdIdentityValue.swift */; };
D093D7EC206413C900BC3599 /* SecureIdIdentityValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7EA206413C900BC3599 /* SecureIdIdentityValue.swift */; };
D093D7EE206413F600BC3599 /* SecureIdDataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7ED206413F600BC3599 /* SecureIdDataTypes.swift */; };
D093D7EF206413F600BC3599 /* SecureIdDataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7ED206413F600BC3599 /* SecureIdDataTypes.swift */; };
D093D7F12064194600BC3599 /* SecureIdIdentityField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F02064194600BC3599 /* SecureIdIdentityField.swift */; };
D093D7F22064194600BC3599 /* SecureIdIdentityField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F02064194600BC3599 /* SecureIdIdentityField.swift */; };
D093D7F520641A4900BC3599 /* SecureIdPhoneField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F420641A4900BC3599 /* SecureIdPhoneField.swift */; };
D093D7F620641A4900BC3599 /* SecureIdPhoneField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F420641A4900BC3599 /* SecureIdPhoneField.swift */; };
D093D7F920641AA500BC3599 /* SecureIdEmailField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F820641AA500BC3599 /* SecureIdEmailField.swift */; };
D093D7FA20641AA500BC3599 /* SecureIdEmailField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F820641AA500BC3599 /* SecureIdEmailField.swift */; };
D093D7F520641A4900BC3599 /* SecureIdPhoneValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F420641A4900BC3599 /* SecureIdPhoneValue.swift */; };
D093D7F620641A4900BC3599 /* SecureIdPhoneValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F420641A4900BC3599 /* SecureIdPhoneValue.swift */; };
D093D7F920641AA500BC3599 /* SecureIdEmailValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F820641AA500BC3599 /* SecureIdEmailValue.swift */; };
D093D7FA20641AA500BC3599 /* SecureIdEmailValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F820641AA500BC3599 /* SecureIdEmailValue.swift */; };
D093D806206539D000BC3599 /* SaveSecureIdValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D805206539D000BC3599 /* SaveSecureIdValue.swift */; };
D099D7461EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; };
D099D7471EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; };
D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */; };
@@ -852,11 +851,11 @@
D08CAA8B1ED81EDF0000FDA8 /* Localizations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localizations.swift; sourceTree = "<group>"; };
D08F4A651E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeInstalledStickerPacksOperations.swift; sourceTree = "<group>"; };
D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeInstalledStickerPacksOperations.swift; sourceTree = "<group>"; };
D093D7EA206413C900BC3599 /* SecureIdPassportIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPassportIdentity.swift; sourceTree = "<group>"; };
D093D7EA206413C900BC3599 /* SecureIdIdentityValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdIdentityValue.swift; sourceTree = "<group>"; };
D093D7ED206413F600BC3599 /* SecureIdDataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdDataTypes.swift; sourceTree = "<group>"; };
D093D7F02064194600BC3599 /* SecureIdIdentityField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdIdentityField.swift; sourceTree = "<group>"; };
D093D7F420641A4900BC3599 /* SecureIdPhoneField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPhoneField.swift; sourceTree = "<group>"; };
D093D7F820641AA500BC3599 /* SecureIdEmailField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdEmailField.swift; sourceTree = "<group>"; };
D093D7F420641A4900BC3599 /* SecureIdPhoneValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPhoneValue.swift; sourceTree = "<group>"; };
D093D7F820641AA500BC3599 /* SecureIdEmailValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdEmailValue.swift; sourceTree = "<group>"; };
D093D805206539D000BC3599 /* SaveSecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveSecureIdValue.swift; sourceTree = "<group>"; };
D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelMessageStateVersionAttribute.swift; sourceTree = "<group>"; };
D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewChannelStateValidation.swift; sourceTree = "<group>"; };
D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerCommands.swift; sourceTree = "<group>"; };
@@ -1497,40 +1496,31 @@
name = Localization;
sourceTree = "<group>";
};
D093D7E82064135300BC3599 /* Fields */ = {
D093D7E82064135300BC3599 /* Values */ = {
isa = PBXGroup;
children = (
D093D7ED206413F600BC3599 /* SecureIdDataTypes.swift */,
D093D7E92064135A00BC3599 /* Identity */,
D093D7F320641A3F00BC3599 /* Phone */,
D093D7F720641A9600BC3599 /* Email */,
D093D7F320641A3F00BC3599 /* Other */,
);
name = Fields;
name = Values;
sourceTree = "<group>";
};
D093D7E92064135A00BC3599 /* Identity */ = {
isa = PBXGroup;
children = (
D093D7F02064194600BC3599 /* SecureIdIdentityField.swift */,
D093D7EA206413C900BC3599 /* SecureIdPassportIdentity.swift */,
D093D7EA206413C900BC3599 /* SecureIdIdentityValue.swift */,
);
name = Identity;
sourceTree = "<group>";
};
D093D7F320641A3F00BC3599 /* Phone */ = {
D093D7F320641A3F00BC3599 /* Other */ = {
isa = PBXGroup;
children = (
D093D7F420641A4900BC3599 /* SecureIdPhoneField.swift */,
D093D7F420641A4900BC3599 /* SecureIdPhoneValue.swift */,
D093D7F820641AA500BC3599 /* SecureIdEmailValue.swift */,
);
name = Phone;
sourceTree = "<group>";
};
D093D7F720641A9600BC3599 /* Email */ = {
isa = PBXGroup;
children = (
D093D7F820641AA500BC3599 /* SecureIdEmailField.swift */,
);
name = Email;
name = Other;
sourceTree = "<group>";
};
D09D8BF71D4FAB1D0081DBEC = {
@@ -1621,10 +1611,11 @@
D0BE303820619E9E00FBE6D8 /* Secure ID */ = {
isa = PBXGroup;
children = (
D0BE303920619EE800FBE6D8 /* SecureIdForm.swift */,
D0BE303C2061A29100FBE6D8 /* RequestSecureIdForm.swift */,
D0BE304A20627D9800FBE6D8 /* AccessSecureId.swift */,
D093D7E82064135300BC3599 /* Fields */,
D0BE303920619EE800FBE6D8 /* SecureIdForm.swift */,
D093D805206539D000BC3599 /* SaveSecureIdValue.swift */,
D093D7E82064135300BC3599 /* Values */,
);
name = "Secure ID";
sourceTree = "<group>";
@@ -1952,7 +1943,7 @@
D0DF0CA81D82BF32008AEB01 /* PeerParticipants.swift in Sources */,
D0FA8BA71E1FA6DF001E855B /* TelegramSecretChat.swift in Sources */,
D03B0D5F1D631A6900955575 /* Serialization.swift in Sources */,
D093D7F920641AA500BC3599 /* SecureIdEmailField.swift in Sources */,
D093D7F920641AA500BC3599 /* SecureIdEmailValue.swift in Sources */,
D0C44B611FC616E200227BE0 /* SearchGroupMembers.swift in Sources */,
D03B0D441D6319F900955575 /* CloudFileMediaResource.swift in Sources */,
D018D3371E648ACF00C5E089 /* CreateChannel.swift in Sources */,
@@ -2046,7 +2037,7 @@
D0F7AB2C1DCE889D009AD9A1 /* EditedMessageAttribute.swift in Sources */,
D0FA8BAA1E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift in Sources */,
D00D97C71E32901700E5C2B6 /* PeerInputActivity.swift in Sources */,
D093D7EB206413C900BC3599 /* SecureIdPassportIdentity.swift in Sources */,
D093D7EB206413C900BC3599 /* SecureIdIdentityValue.swift in Sources */,
D0FA8BAD1E1FD6E2001E855B /* MemoryBufferExtensions.swift in Sources */,
D03B0CBF1D62234A00955575 /* Log.swift in Sources */,
C2FD33E41E687BF1008D13D4 /* PeerPhotoUpdater.swift in Sources */,
@@ -2082,7 +2073,7 @@
D033FEB61E61F3F900644997 /* BlockedPeers.swift in Sources */,
D00C7CCC1E3620C30080C3D5 /* CachedChannelParticipants.swift in Sources */,
D09BB6B41DB02C2B00A905C0 /* PendingMessageManager.swift in Sources */,
D093D7F520641A4900BC3599 /* SecureIdPhoneField.swift in Sources */,
D093D7F520641A4900BC3599 /* SecureIdPhoneValue.swift in Sources */,
D0B167231F9F972E00976B40 /* LoggingSettings.swift in Sources */,
D0BC387B1E40D2880044D6FE /* TogglePeerChatPinned.swift in Sources */,
D0BB7C5A1E5C8074001527C3 /* ChannelParticipants.swift in Sources */,
@@ -2103,6 +2094,7 @@
D0C26D6C1FE286C3004ABF18 /* FetchChatList.swift in Sources */,
D0B843831DA6EDB8005F29E1 /* CachedGroupData.swift in Sources */,
D0E35A121DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */,
D093D806206539D000BC3599 /* SaveSecureIdValue.swift in Sources */,
C239BE9C1E630CA700C2C453 /* UpdatePinnedMessage.swift in Sources */,
D08CAA7D1ED77EE90000FDA8 /* LocalizationSettings.swift in Sources */,
D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */,
@@ -2156,7 +2148,6 @@
D021E0E21DB5401A00C6B04F /* StickerManagement.swift in Sources */,
D0BC38701E40853E0044D6FE /* UpdatePeers.swift in Sources */,
D0F3A8A81E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */,
D093D7F12064194600BC3599 /* SecureIdIdentityField.swift in Sources */,
D03B0CE21D62249B00955575 /* InlineBotMessageAttribute.swift in Sources */,
D0AB0B9A1D666520002C78E7 /* ManagedSynchronizePeerReadStates.swift in Sources */,
D03B0D5B1D631A6900955575 /* Buffer.swift in Sources */,
@@ -2223,7 +2214,6 @@
C26A37EF1E5E0C41006977AC /* ChannelParticipants.swift in Sources */,
D00D343D1E6EC9770057B307 /* TelegramMediaGame.swift in Sources */,
D0C26D6A1FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift in Sources */,
D093D7F22064194600BC3599 /* SecureIdIdentityField.swift in Sources */,
D01C7F051EFC1C49008305F1 /* DeviceContact.swift in Sources */,
D050F26A1E4A5B6D00988324 /* ManagedGlobalNotificationSettings.swift in Sources */,
D050F26B1E4A5B6D00988324 /* ApplyMaxReadIndexInteractively.swift in Sources */,
@@ -2344,10 +2334,10 @@
D0B8442A1DAB91E0005F29E1 /* NBAsYouTypeFormatter.m in Sources */,
D07047B51F3DF1FE00F6A8D4 /* ConsumablePersonalMentionMessageAttribute.swift in Sources */,
D0448C8F1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift in Sources */,
D093D7F620641A4900BC3599 /* SecureIdPhoneField.swift in Sources */,
D093D7F620641A4900BC3599 /* SecureIdPhoneValue.swift in Sources */,
D0C26D6D1FE286C3004ABF18 /* FetchChatList.swift in Sources */,
D073CE6E1DCBCF17007511FD /* ForwardSourceInfoAttribute.swift in Sources */,
D093D7FA20641AA500BC3599 /* SecureIdEmailField.swift in Sources */,
D093D7FA20641AA500BC3599 /* SecureIdEmailValue.swift in Sources */,
D05A32E21E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift in Sources */,
D0613FD01E60520700202CDB /* ChannelMembers.swift in Sources */,
D001F3E81E128A1C007A8C60 /* ChannelState.swift in Sources */,
@@ -2463,7 +2453,7 @@
D099D7471EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */,
D058E0D21E8AD65C00A442DE /* StandaloneSendMessage.swift in Sources */,
D03C536F1DAD5CA9004C17B3 /* BotInfo.swift in Sources */,
D093D7EC206413C900BC3599 /* SecureIdPassportIdentity.swift in Sources */,
D093D7EC206413C900BC3599 /* SecureIdIdentityValue.swift in Sources */,
D0FA8BBA1E2240B4001E855B /* SecretChatIncomingDecryptedOperation.swift in Sources */,
D033FEB41E61F3C000644997 /* ReportPeer.swift in Sources */,
D0FA8BAE1E1FD6E2001E855B /* MemoryBufferExtensions.swift in Sources */,

View File

@@ -15,11 +15,58 @@ private enum GenerateSecureSecretError {
case generic
}
func decryptedSecureSecret(encryptedSecretData: Data, password: String) -> Data? {
func encryptSecureData(key: Data, iv: Data, data: Data, decrypt: Bool) -> Data? {
if data.count % 16 != 0 {
return nil
}
var processedData = Data(count: data.count)
guard processedData.withUnsafeMutableBytes({ (processedDataBytes: UnsafeMutablePointer<Int8>) -> Bool in
return key.withUnsafeBytes { (keyBytes: UnsafePointer<Int8>) -> Bool in
return iv.withUnsafeBytes { (ivBytes: UnsafePointer<Int8>) -> Bool in
return data.withUnsafeBytes { (dataBytes: UnsafePointer<Int8>) -> Bool in
var processedCount: Int = 0
let result = CCCrypt(CCOperation(decrypt ? kCCDecrypt : kCCEncrypt), CCAlgorithm(kCCAlgorithmAES128), 0, keyBytes, key.count, ivBytes, dataBytes, data.count, processedDataBytes, processedData.count, &processedCount)
if result != kCCSuccess {
return false
}
if processedCount != processedData.count {
return false
}
return true
}
}
}
}) else {
return nil
}
return processedData
}
func verifySecureSecret(_ data: Data) -> Bool {
guard data.withUnsafeBytes({ (bytes: UnsafePointer<UInt8>) -> Bool in
var checksum: UInt32 = 0
for i in 0 ..< data.count {
checksum += UInt32(bytes.advanced(by: i).pointee)
checksum = checksum % 255
}
if checksum == 239 {
return true
} else {
return false
}
}) else {
return false
}
return true
}
func decryptedSecureSecret(encryptedSecretData: Data, password: String, salt: Data, hash: Int64) -> Data? {
guard let passwordData = password.data(using: .utf8) else {
return nil
}
let passwordHash = sha512Digest(passwordData)
let passwordHash = sha512Digest(salt + passwordData + salt)
let secretKey = passwordHash.subdata(in: 0 ..< 32)
let iv = passwordHash.subdata(in: 32 ..< (32 + 16))
@@ -45,30 +92,45 @@ func decryptedSecureSecret(encryptedSecretData: Data, password: String) -> Data?
return nil
}
guard decryptedSecret.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<UInt8>) -> Bool in
var checksum: UInt32 = 0
for i in 0 ..< decryptedSecret.count {
checksum += UInt32(bytes.advanced(by: i).pointee)
checksum = checksum % 255
if !verifySecureSecret(decryptedSecret) {
return nil
}
if checksum == 239 {
return true
} else {
return false
let secretHashData = sha256Digest(decryptedSecret)
var secretHash: Int64 = 0
secretHashData.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
memcpy(&secretHash, bytes.advanced(by: secretHashData.count - 8), 8)
}
}) else {
if secretHash != hash {
return nil
}
return decryptedSecret
}
func encryptedSecureSecret(secretData: Data, password: String) -> Data? {
func encryptedSecureSecret(secretData: Data, password: String, inputSalt: Data) -> (data: Data, salt: Data, hash: Int64)? {
let secretHashData = sha256Digest(secretData)
var secretHash: Int64 = 0
secretHashData.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
memcpy(&secretHash, bytes.advanced(by: secretHashData.count - 8), 8)
}
guard let passwordData = password.data(using: .utf8) else {
return nil
}
let passwordHash = sha512Digest(passwordData)
var randomSalt = Data(count: 8)
guard randomSalt.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<Int8>) -> Bool in
let result = SecRandomCopyBytes(nil, randomSalt.count, bytes)
return result == errSecSuccess
}) else {
return nil
}
let secretSalt = inputSalt + randomSalt
let passwordHash = sha512Digest(secretSalt + passwordData + secretSalt)
let secretKey = passwordHash.subdata(in: 0 ..< 32)
let iv = passwordHash.subdata(in: 32 ..< (32 + 16))
@@ -94,20 +156,20 @@ func encryptedSecureSecret(secretData: Data, password: String) -> Data? {
return nil
}
if decryptedSecureSecret(encryptedSecretData: encryptedSecret, password: password) != secretData {
if decryptedSecureSecret(encryptedSecretData: encryptedSecret, password: password, salt: secretSalt, hash: secretHash) != secretData {
return nil
}
return encryptedSecret
return (encryptedSecret, secretSalt, secretHash)
}
private func generateSecureSecret(network: Network, password: String) -> Signal<Data, GenerateSecureSecretError> {
func generateSecureSecretData() -> Data? {
var secretData = Data(count: 32)
guard secretData.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<Int8>) -> Bool in
let copyResult = SecRandomCopyBytes(nil, 32, bytes)
return copyResult == errSecSuccess
}) else {
return .fail(.generic)
return nil
}
secretData.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<UInt8>) in
@@ -136,12 +198,15 @@ private func generateSecureSecret(network: Network, password: String) -> Signal<
}
}
})
return secretData
}
guard let encryptedSecret = encryptedSecureSecret(secretData: secretData, password: password) else {
private func generateSecureSecret(network: Network, password: String) -> Signal<Data, GenerateSecureSecretError> {
guard let secretData = generateSecureSecretData() else {
return .fail(.generic)
}
return updateTwoStepVerificationSecureSecret(network: network, password: password, updatedSecret: encryptedSecret)
return updateTwoStepVerificationSecureSecret(network: network, password: password, secret: secretData)
|> mapError { _ -> GenerateSecureSecretError in
return .generic
}
@@ -152,6 +217,7 @@ private func generateSecureSecret(network: Network, password: String) -> Signal<
public struct SecureIdAccessContext {
let secret: Data
let hash: Int64
}
public enum SecureIdAccessError {
@@ -167,8 +233,8 @@ public func accessSecureId(network: Network, password: String) -> Signal<SecureI
}
|> mapToSignal { settings -> Signal<SecureIdAccessContext, SecureIdAccessError> in
if let secureSecret = settings.secureSecret {
if let decryptedSecret = decryptedSecureSecret(encryptedSecretData: secureSecret, password: "q") { //password
return .single(SecureIdAccessContext(secret: decryptedSecret))
if let decryptedSecret = decryptedSecureSecret(encryptedSecretData: secureSecret.data, password: password, salt: secureSecret.salt, hash: secureSecret.hash) {
return .single(SecureIdAccessContext(secret: decryptedSecret, hash: secureSecret.hash))
} else {
return .fail(.secretPasswordMismatch)
}
@@ -178,7 +244,12 @@ public func accessSecureId(network: Network, password: String) -> Signal<SecureI
return SecureIdAccessError.generic
}
|> map { decryptedSecret in
return SecureIdAccessContext(secret: decryptedSecret)
let secretHashData = sha256Digest(decryptedSecret)
var secretHash: Int64 = 0
secretHashData.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
memcpy(&secretHash, bytes.advanced(by: secretHashData.count - 8), 8)
}
return SecureIdAccessContext(secret: decryptedSecret, hash: secretHash)
}
}
}

View File

@@ -342,20 +342,47 @@ public struct TwoStepAuthData {
public let currentHint: String?
public let unconfirmedEmailPattern: String?
public let secretRandom: Data
public let nextSecureSalt: Data
}
public func twoStepAuthData(_ network: Network) -> Signal<TwoStepAuthData, MTRpcError> {
return network.request(Api.functions.account.getPassword())
|> map { config -> TwoStepAuthData in
switch config {
case let .noPassword(newSalt, secretRandom, emailUnconfirmedPattern):
return TwoStepAuthData(nextSalt: newSalt.makeData(), currentSalt: nil, hasRecovery: false, currentHint: nil, unconfirmedEmailPattern: emailUnconfirmedPattern, secretRandom: secretRandom.makeData())
case let .password(currentSalt, newSalt, secretRandom, hint, hasRecovery, emailUnconfirmedPattern):
return TwoStepAuthData(nextSalt: newSalt.makeData(), currentSalt: currentSalt.makeData(), hasRecovery: hasRecovery == .boolTrue, currentHint: hint, unconfirmedEmailPattern: emailUnconfirmedPattern, secretRandom: secretRandom.makeData())
case let .noPassword(newSalt, newSecureSalt, secretRandom, emailUnconfirmedPattern):
return TwoStepAuthData(nextSalt: newSalt.makeData(), currentSalt: nil, hasRecovery: false, currentHint: nil, unconfirmedEmailPattern: emailUnconfirmedPattern, secretRandom: secretRandom.makeData(), nextSecureSalt: newSecureSalt.makeData())
case let .password(currentSalt, newSalt, newSecureSalt, secretRandom, hint, hasRecovery, emailUnconfirmedPattern):
return TwoStepAuthData(nextSalt: newSalt.makeData(), currentSalt: currentSalt.makeData(), hasRecovery: hasRecovery == .boolTrue, currentHint: hint, unconfirmedEmailPattern: emailUnconfirmedPattern, secretRandom: secretRandom.makeData(), nextSecureSalt: newSecureSalt.makeData())
}
}
}
func hexString(_ data: Data) -> String {
let hexString = NSMutableString()
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
for i in 0 ..< data.count {
hexString.appendFormat("%02x", UInt(bytes.advanced(by: i).pointee))
}
}
return hexString as String
}
func dataWithHexString(_ string: String) -> Data {
var hex = string
var data = Data()
while hex.count > 0 {
let subIndex = hex.index(hex.startIndex, offsetBy: 2)
let c = String(hex[..<subIndex])
hex = String(hex[subIndex...])
var ch: UInt32 = 0
Scanner(string: c).scanHexInt32(&ch)
var char = UInt8(ch)
data.append(&char, count: 1)
}
return data
}
func sha1Digest(_ data : Data) -> Data {
var res = Data()
res.count = Int(CC_SHA1_DIGEST_LENGTH)
@@ -467,6 +494,81 @@ public struct AccountRunningImportantTasks: OptionSet {
public static let pendingMessages = AccountRunningImportantTasks(rawValue: 1 << 1)
}
private struct MasterNotificationKey {
let id: Data
let data: Data
}
private func masterNotificationsKey(account: Account, ignoreDisabled: Bool) -> Signal<MasterNotificationKey, NoError> {
if let key = account.masterNotificationKey.with({ $0 }) {
//return .single(key)
}
return account.postbox.modify(ignoreDisabled: ignoreDisabled, { modifier -> MasterNotificationKey in
if let value = modifier.keychainEntryForKey("master-notification-secret"), !value.isEmpty {
let authKeyHash = sha1Digest(value)
let authKeyId = authKeyHash.subdata(in: authKeyHash.count - 8 ..< authKeyHash.count)
let keyData = MasterNotificationKey(id: authKeyId, data: value)
let _ = account.masterNotificationKey.swap(keyData)
return keyData
} else {
var secretData = Data(count: 256)
if !secretData.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<Int8>) -> Bool in
let copyResult = SecRandomCopyBytes(nil, secretData.count, bytes)
return copyResult == errSecSuccess
}) {
assertionFailure()
}
modifier.setKeychainEntry(secretData, forKey: "master-notification-secret")
let authKeyHash = sha1Digest(secretData)
let authKeyId = authKeyHash.subdata(in: authKeyHash.count - 8 ..< authKeyHash.count)
let keyData = MasterNotificationKey(id: authKeyId, data: secretData)
let _ = account.masterNotificationKey.swap(keyData)
return keyData
}
})
}
public func decryptedNotificationPayload(account: Account, data: Data) -> Signal<Data?, NoError> {
return masterNotificationsKey(account: account, ignoreDisabled: true)
|> map { secret -> Data? in
if data.subdata(in: 0 ..< 8) != secret.id {
return nil
}
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 + secret.data.subdata(in: x ..< (x + 36)))
let sha256_b = sha256Digest(secret.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(secret.data.subdata(in: (88 + x) ..< (88 + x + 32)) + data)
let checkMsgKey = checkMsgKeyLarge.subdata(in: 8 ..< (8 + 16))
if checkMsgKey != msgKey {
return nil
}
return data.subdata(in: 4 ..< (4 + Int(dataLength)))
}
}
public class Account {
public let id: AccountRecordId
public let basePath: String
@@ -524,6 +626,8 @@ public class Account {
return self._importantTasksRunning.get()
}
fileprivate let masterNotificationKey = Atomic<MasterNotificationKey?>(value: nil)
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
public init(id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods) {
@@ -588,12 +692,16 @@ public class Account {
#if DEBUG
appSandbox = .boolTrue
#endif
return network.request(Api.functions.account.registerDevice(tokenType: 1, token: tokenString, appSandbox: appSandbox, secret: Buffer(), otherUids: []))
return masterNotificationsKey(account: self, ignoreDisabled: false)
|> mapToSignal { secret -> Signal<Void, NoError> in
return network.request(Api.functions.account.registerDevice(tokenType: 1, token: tokenString, appSandbox: appSandbox, secret: Buffer(data: secret.data), otherUids: []))
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
}
self.notificationTokenDisposable.set(appliedNotificationToken.start())
let appliedVoipToken = self.voipToken.get()
@@ -612,12 +720,15 @@ public class Account {
appSandbox = .boolTrue
#endif
return network.request(Api.functions.account.registerDevice(tokenType: 9, token: tokenString, appSandbox: appSandbox, secret: Buffer(), otherUids: []))
return masterNotificationsKey(account: self, ignoreDisabled: false)
|> mapToSignal { secret -> Signal<Void, NoError> in
return network.request(Api.functions.account.registerDevice(tokenType: 9, token: tokenString, appSandbox: appSandbox, secret: Buffer(data: secret.data), otherUids: []))
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
}
self.voipTokenDisposable.set(appliedVoipToken.start())
let serviceTasksMasterBecomeMaster = shouldBeServiceTaskMaster.get()

File diff suppressed because it is too large Load Diff

View File

@@ -156,7 +156,7 @@ public func authorizeWithCode(account: UnauthorizedAccount, code: String) -> Sig
switch result {
case .noPassword:
return .fail(.generic)
case let .password(_, _, _, hint, _, _):
case let .password(_, _, _, _, hint, _, _):
return .single(.password(hint: hint))
}
}

View File

@@ -13,139 +13,87 @@ public enum RequestSecureIdFormError {
case generic
}
public enum RequestedSecureIdField {
case identity
case address
case phone
case email
}
private func parseRequestedFieldType(_ type: Api.AuthFieldType) -> RequestedSecureIdField {
private func parseSecureValueType(_ type: Api.SecureValueType) -> SecureIdRequestedFormField {
switch type {
case .authFieldTypeIdentity:
case .secureValueTypeIdentity:
return .identity
case .authFieldTypeAddress:
case .secureValueTypeAddress:
return .address
case .authFieldTypePhone:
case .secureValueTypePhone:
return .phone
case .authFieldTypeEmail:
case .secureValueTypeEmail:
return .email
}
}
private func parseFileReference(_ file: Api.SecureFile) -> SecureIdFileReference {
switch file {
case .secureFileEmpty:
return .none
case let .secureFile(id, accessHash, size, dcId, fileHash):
return .file(id: id, accessHash: accessHash, size: size, datacenterId: dcId, fileHash: fileHash)
//secureData data:bytes data_hash:bytes = SecureData;
private func parseSecureData(_ value: Api.SecureData) -> (data: Data, hash: Data) {
switch value {
case let .secureData(data, dataHash):
return (data.makeData(), dataHash.makeData())
}
}
/*private func parseValue(_ value: Api.SecureValue) -> SecureIdFieldValue {
private func parseSecureValue(context: SecureIdAccessContext, value: Api.SecureValue) -> SecureIdValue? {
switch value {
case let .secureValueEmpty(name):
return SecureIdFieldValue(name: name, data: .none)
case let .secureValueData(name, data, hash, secret):
return SecureIdFieldValue(name: name, data: .data(data: data.makeData(), hash: hash, secret: secret.makeData()))
case let .secureValueFile(name, file, hash, secret):
return SecureIdFieldValue(name: name, data: .files(files: file.map(parseFileReference), hash: hash, secret: secret.makeData()))
case let .secureValueText(name, text, hash):
return SecureIdFieldValue(name: name, data: .text(text: text, hash: hash))
}
}*/
private func parseIdentityField(context: SecureIdAccessContext, value: Api.SecureValue, document: Api.SecureValue?) -> SecureIdIdentityField? {
switch value {
case let .secureValueData(name, data, hash, secret):
return nil
default:
case let .secureValueIdentity(_, data, files, secret, hash, verified):
let (encryptedData, encryptedHash) = parseSecureData(data)
guard let decryptedData = decryptedSecureData(context: context, data: encryptedData, dataHash: encryptedHash, encryptedSecret: secret.makeData()) else {
return nil
}
var fileReferences: [Int64: SecureIdFileReference] = [:]
for file in files.map(SecureIdFileReference.init).flatMap({ $0 }) {
fileReferences[file.id] = file
}
private func parsePhoneField(context: SecureIdAccessContext, value: Api.SecureValue) -> SecureIdPhoneField? {
switch value {
case let .secureValueText(name, text, _):
return SecureIdPhoneField(rawValue: text)
default:
guard let value = SecureIdIdentityValue(data: decryptedData, fileReferences: fileReferences) else {
return nil
}
}
private func parseEmailField(context: SecureIdAccessContext, value: Api.SecureValue) -> SecureIdEmailField? {
switch value {
case let .secureValueText(name, text, _):
return SecureIdEmailField(rawValue: text)
default:
return .identity(value)
case let .secureValueAddress(_, data, files, secret, hash, verified):
return nil
case let .secureValuePhone(_, phone, hash, verified):
guard let phoneData = phone.data(using: .utf8) else {
return nil
}
if sha256Digest(phoneData) != hash.makeData() {
return nil
}
return .phone(SecureIdPhoneValue(phone: phone))
case let .secureValueEmail(_, email, hash, verified):
guard let emailData = email.data(using: .utf8) else {
return nil
}
if sha256Digest(emailData) != hash.makeData() {
return nil
}
return .email(SecureIdEmailValue(email: email))
}
}
private func parseFields(context: SecureIdAccessContext, fields: [Api.AuthField]) -> SecureIdFields {
var result = SecureIdFields(identity: nil, phone: nil, email: nil)
for field in fields {
switch field {
case let .authField(_, type, data, document):
switch type {
case .authFieldTypeIdentity:
if let identity = parseIdentityField(context: context, value: data, document: document) {
result.identity = .value(identity)
} else {
result.identity = .empty
}
case .authFieldTypeAddress:
break
case .authFieldTypePhone:
if let phone = parsePhoneField(context: context, value: data) {
result.phone = .value(phone)
} else {
result.phone = .empty
}
case .authFieldTypeEmail:
if let email = parseEmailField(context: context, value: data) {
result.email = .value(email)
} else {
result.email = .empty
}
}
}
}
return result
private func parseSecureValues(context: SecureIdAccessContext, values: [Api.SecureValue]) -> [SecureIdValue] {
return values.map({ parseSecureValue(context: context, value: $0) }).flatMap({ $0 })
}
public struct EncryptedSecureIdForm {
public let peerId: PeerId
public let requestedFields: [RequestedSecureIdField]
public let requestedFields: [SecureIdRequestedFormField]
let encryptedFields: [Api.AuthField]
let encryptedValues: [Api.SecureValue]
}
public func requestSecureIdForm(postbox: Postbox, network: Network, peerId: PeerId, scope: [String], origin: String?, packageName: String?, bundleId: String?, publicKey: String?) -> Signal<EncryptedSecureIdForm, RequestSecureIdFormError> {
public func requestSecureIdForm(postbox: Postbox, network: Network, peerId: PeerId, scope: String, publicKey: String) -> Signal<EncryptedSecureIdForm, RequestSecureIdFormError> {
if peerId.namespace != Namespaces.Peer.CloudUser {
return .fail(.generic)
}
var flags: Int32 = 0
if let _ = origin {
flags |= 1 << 0
}
if let _ = packageName {
flags |= 1 << 1
}
if let _ = bundleId {
flags |= 1 << 2
}
if let _ = publicKey {
flags |= 1 << 3
}
return network.request(Api.functions.account.getAuthorizationForm(flags: flags, botId: peerId.id, scope: scope, origin: origin, packageName: packageName, bundleId: bundleId, publicKey: publicKey))
return network.request(Api.functions.account.getAuthorizationForm(botId: peerId.id, scope: scope, publicKey: publicKey))
|> mapError { _ -> RequestSecureIdFormError in
return .generic
}
|> mapToSignal { result -> Signal<EncryptedSecureIdForm, RequestSecureIdFormError> in
return postbox.modify { modifier -> EncryptedSecureIdForm in
switch result {
case let .authorizationForm(_, botId, fields, _, users):
case let .authorizationForm(_, requiredTypes, values, users):
var peers: [Peer] = []
for user in users {
let parsed = TelegramUser(user: user)
@@ -155,17 +103,12 @@ public func requestSecureIdForm(postbox: Postbox, network: Network, peerId: Peer
return updated
})
return EncryptedSecureIdForm(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), requestedFields: fields.map { field -> RequestedSecureIdField in
switch field {
case let .authField(_, type, data, document):
return parseRequestedFieldType(type)
}
}, encryptedFields: fields)
return EncryptedSecureIdForm(peerId: peerId, requestedFields: requiredTypes.map(parseSecureValueType), encryptedValues: values)
}
} |> mapError { _ in return RequestSecureIdFormError.generic }
}
}
public func decryptedSecureIdForm(context: SecureIdAccessContext, form: EncryptedSecureIdForm) -> SecureIdForm? {
return SecureIdForm(peerId: form.peerId, fields: parseFields(context: context, fields: form.encryptedFields))
return SecureIdForm(peerId: form.peerId, requestedFields: form.requestedFields, values: parseSecureValues(context: context, values: form.encryptedValues))
}

View File

@@ -0,0 +1,150 @@
import Foundation
#if os(macOS)
import PostboxMac
import MtProtoKitMac
import SwiftSignalKitMac
#else
import Postbox
import MtProtoKitDynamic
import SwiftSignalKit
#endif
public enum SaveSecureIdValueError {
case generic
}
private func paddedData(_ data: Data) -> Data {
var paddingCount = Int(47 + arc4random_uniform(255 - 47))
paddingCount -= ((data.count + paddingCount) % 16)
var result = Data(count: paddingCount + data.count)
result.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
bytes.advanced(by: 0).pointee = UInt8(paddingCount)
arc4random_buf(bytes.advanced(by: 1), paddingCount - 1)
data.withUnsafeBytes { (source: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: paddingCount), source, data.count)
}
}
return result
}
private func unpaddedData(_ data: Data) -> Data? {
var paddingCount: UInt8 = 0
data.copyBytes(to: &paddingCount, count: 1)
if paddingCount < 0 || paddingCount > data.count {
return nil
}
return data.subdata(in: Int(paddingCount) ..< data.count)
}
struct EncryptedSecureData {
let data: Data
let hash: Data
let encryptedSecret: Data
let secretHash: Data
}
func encryptedSecureData(context: SecureIdAccessContext, data: Data) -> EncryptedSecureData? {
let fileData = paddedData(data)
let fileHash = sha256Digest(fileData)
guard let fileSecret = generateSecureSecretData() 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 encryptedFileData = encryptSecureData(key: fileKey, iv: fileIv, data: fileData, decrypt: false) else {
return nil
}
let secretHash = sha512Digest(context.secret)
let secretKey = secretHash.subdata(in: 0 ..< 32)
let secretIv = secretHash.subdata(in: 32 ..< (32 + 16))
guard let encryptedFileSecret = encryptSecureData(key: secretKey, iv: secretIv, data: fileSecret, decrypt: false) else {
return nil
}
return EncryptedSecureData(data: encryptedFileData, hash: fileHash, encryptedSecret: encryptedFileSecret, secretHash: secretHash)
}
func decryptedSecureData(context: SecureIdAccessContext, data: Data, dataHash: Data, encryptedSecret: Data) -> Data? {
let secretHash = sha512Digest(context.secret)
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
}
if !verifySecureSecret(fileSecret) {
return nil
}
let fileSecretHash = sha512Digest(fileSecret + dataHash)
let fileKey = fileSecretHash.subdata(in: 0 ..< 32)
let fileIv = fileSecretHash.subdata(in: 32 ..< (32 + 16))
guard let decryptedFileData = encryptSecureData(key: fileKey, iv: fileIv, data: data, decrypt: true) else {
return nil
}
let checkDataHash = sha256Digest(decryptedFileData)
if checkDataHash != dataHash {
return nil
}
return unpaddedData(decryptedFileData)
}
private func makeInputSecureValue(context: SecureIdAccessContext, value: SecureIdValue) -> Api.InputSecureValue? {
switch value {
case .identity:
guard let (decryptedData, fileReferences) = value.serialize() else {
return nil
}
guard let encryptedData = encryptedSecureData(context: context, data: decryptedData) else {
return nil
}
if let checkData = decryptedSecureData(context: context, data: encryptedData.data, dataHash: encryptedData.hash, encryptedSecret: encryptedData.encryptedSecret) {
if checkData != decryptedData {
return nil
}
} else {
return nil
}
let files = fileReferences.map { file in
return Api.InputSecureFile.inputSecureFile(id: file.id, accessHash: file.accessHash)
}
return Api.InputSecureValue.inputSecureValueIdentity(data: Api.SecureData.secureData(data: Buffer(data: encryptedData.data), dataHash: Buffer(data: encryptedData.hash)), files: files, secret: Buffer(data: encryptedData.encryptedSecret), hash: Buffer(data: encryptedData.secretHash))
case let .phone(value):
guard let phoneData = value.phone.data(using: .utf8) else {
return nil
}
return Api.InputSecureValue.inputSecureValuePhone(phone: value.phone, hash: Buffer(data: sha256Digest(phoneData)))
case let .email(value):
guard let emailData = value.email.data(using: .utf8) else {
return nil
}
return Api.InputSecureValue.inputSecureValueEmail(email: value.email, hash: Buffer(data: sha256Digest(emailData)))
}
}
public func saveSecureIdValue(network: Network, context: SecureIdAccessContext, value: SecureIdValue) -> Signal<Void, SaveSecureIdValueError> {
guard let inputValue = makeInputSecureValue(context: context, value: value) else {
return .fail(.generic)
}
return network.request(Api.functions.account.saveSecureValue(value: inputValue, secureSecretHash: context.hash))
|> mapError { _ -> SaveSecureIdValueError in
return .generic
}
|> mapToSignal { _ -> Signal<Void, SaveSecureIdValueError> in
return .complete()
}
}

View File

@@ -1,7 +1,7 @@
import Foundation
public struct SecureIdDate: Equatable {
private var timestamp: Int32
public let timestamp: Int32
public init(timestamp: Int32) {
self.timestamp = timestamp
@@ -20,24 +20,40 @@ public enum SecureIdGender {
case female
}
public enum SecureIdFileReference: Equatable {
case none
case file(id: Int64, accessHash: Int64, size: Int32, datacenterId: Int32, fileHash: String)
public struct SecureIdFileReference: Equatable {
let id: Int64
let accessHash: Int64
let size: Int32
let datacenterId: Int32
let fileHash: Data
public static func ==(lhs: SecureIdFileReference, rhs: SecureIdFileReference) -> Bool {
switch lhs {
case .none:
if case .none = rhs {
return true
} else {
if lhs.id != rhs.id {
return false
}
case let .file(id, accessHash, size, datacenterId, fileHash):
if case .file(id, accessHash, size, datacenterId, fileHash) = rhs {
return true
} else {
if lhs.accessHash != rhs.accessHash {
return false
}
if lhs.size != rhs.size {
return false
}
if lhs.datacenterId != rhs.datacenterId {
return false
}
if lhs.fileHash != rhs.fileHash {
return false
}
return true
}
}
extension SecureIdFileReference {
init?(apiFile: Api.SecureFile) {
switch apiFile {
case let .secureFile(id, accessHash, size, dcId, fileHash):
self.init(id: id, accessHash: accessHash, size: size, datacenterId: dcId, fileHash: fileHash.makeData())
case .secureFileEmpty:
return nil
}
}
}

View File

@@ -1,16 +0,0 @@
import Foundation
public struct SecureIdEmailField: Equatable {
public let rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
public static func ==(lhs: SecureIdEmailField, rhs: SecureIdEmailField) -> Bool {
if lhs.rawValue != rhs.rawValue {
return false
}
return true
}
}

View File

@@ -0,0 +1,16 @@
import Foundation
public struct SecureIdEmailValue: Equatable {
public let email: String
public init(email: String) {
self.email = email
}
public static func ==(lhs: SecureIdEmailValue, rhs: SecureIdEmailValue) -> Bool {
if lhs.email != rhs.email {
return false
}
return true
}
}

View File

@@ -5,77 +5,72 @@ import Foundation
import Postbox
#endif
public enum SecureIdFieldValue<T>: Equatable where T: Equatable {
case empty
case value(T)
public enum SecureIdValue: Equatable {
case identity(SecureIdIdentityValue)
case phone(SecureIdPhoneValue)
case email(SecureIdEmailValue)
public static func ==(lhs: SecureIdFieldValue<T>, rhs: SecureIdFieldValue<T>) -> Bool {
public static func ==(lhs: SecureIdValue, rhs: SecureIdValue) -> Bool {
switch lhs {
case .empty:
if case .empty = rhs {
case let .identity(value):
if case .identity(value) = rhs {
return true
} else {
return false
}
case let .value(value):
if case .value(value) = rhs {
case let .phone(value):
if case .phone(value) = rhs {
return true
} else {
return false
}
case let .email(value):
if case .email(value) = rhs {
return true
} else {
return false
}
}
}
}
public struct SecureIdFields: Equatable {
public var identity: SecureIdFieldValue<SecureIdIdentityField>?
public var phone: SecureIdFieldValue<SecureIdPhoneField>?
public var email: SecureIdFieldValue<SecureIdEmailField>?
public static func ==(lhs: SecureIdFields, rhs: SecureIdFields) -> Bool {
if lhs.identity != rhs.identity {
return false
}
if lhs.phone != rhs.phone {
return false
}
if lhs.email != rhs.email {
return false
}
return true
}
}
public enum SecureIdField: Equatable {
case identity(SecureIdIdentityField)
case phone(SecureIdPhoneField)
case email(SecureIdEmailField)
public static func ==(lhs: SecureIdField, rhs: SecureIdField) -> Bool {
switch lhs {
case let .identity(field):
if case .identity(field) = rhs {
return true
} else {
return false
}
case let .phone(field):
if case .phone(field) = rhs {
return true
} else {
return false
}
case let .email(field):
if case .email(field) = rhs {
return true
} else {
return false
}
func serialize() -> (Data, [SecureIdFileReference])? {
switch self {
case let .identity(value):
return value.serialize()
case .phone, .email:
return nil
}
}
}
public struct SecureIdForm {
public enum SecureIdRequestedFormField {
case identity
case address
case phone
case email
}
public struct SecureIdForm: Equatable {
public let peerId: PeerId
public let fields: SecureIdFields
public let requestedFields: [SecureIdRequestedFormField]
public let values: [SecureIdValue]
public init(peerId: PeerId, requestedFields: [SecureIdRequestedFormField], values: [SecureIdValue]) {
self.peerId = peerId
self.requestedFields = requestedFields
self.values = values
}
public static func ==(lhs: SecureIdForm, rhs: SecureIdForm) -> Bool {
if lhs.peerId != rhs.peerId {
return false
}
if lhs.requestedFields != rhs.requestedFields {
return false
}
if lhs.values != rhs.values {
return false
}
return true
}
}

View File

@@ -1,16 +0,0 @@
import Foundation
public struct SecureIdIdentityField: Equatable {
public var passport: SecureIdPassportIdentity?
public init(passport: SecureIdPassportIdentity?) {
self.passport = passport
}
public static func ==(lhs: SecureIdIdentityField, rhs: SecureIdIdentityField) -> Bool {
if lhs.passport != rhs.passport {
return false
}
return true
}
}

View File

@@ -0,0 +1,227 @@
import Foundation
public enum SecureIdIdentityValue: Equatable {
case passport(SecureIdIdentityPassportValue)
public static func ==(lhs: SecureIdIdentityValue, rhs: SecureIdIdentityValue) -> Bool {
switch lhs {
case let .passport(value):
if case .passport(value) = rhs {
return true
} else {
return false
}
}
}
}
public struct SecureIdIdentityPassportValue: Equatable {
public var identifier: String
public var firstName: String
public var lastName: String
public var birthdate: SecureIdDate
public var countryCode: String
public var gender: SecureIdGender
public var issueDate: SecureIdDate
public var expiryDate: SecureIdDate?
public var verificationDocuments: [SecureIdFileReference]
public init(identifier: String, firstName: String, lastName: String, birthdate: SecureIdDate, countryCode: String, gender: SecureIdGender, issueDate: SecureIdDate, expiryDate: SecureIdDate?, verificationDocuments: [SecureIdFileReference]) {
self.identifier = identifier
self.firstName = firstName
self.lastName = lastName
self.birthdate = birthdate
self.countryCode = countryCode
self.gender = gender
self.issueDate = issueDate
self.expiryDate = expiryDate
self.verificationDocuments = verificationDocuments
}
public static func ==(lhs: SecureIdIdentityPassportValue, rhs: SecureIdIdentityPassportValue) -> Bool {
if lhs.identifier != rhs.identifier {
return false
}
if lhs.firstName != rhs.firstName {
return false
}
if lhs.lastName != rhs.lastName {
return false
}
if lhs.birthdate != rhs.birthdate {
return false
}
if lhs.countryCode != rhs.countryCode {
return false
}
if lhs.gender != rhs.gender {
return false
}
if lhs.issueDate != rhs.issueDate {
return false
}
if lhs.expiryDate != rhs.expiryDate {
return false
}
return true
}
}
private func parseGender(_ string: String) -> SecureIdGender? {
switch string {
case "male":
return .male
case "female":
return .female
default:
return nil
}
}
private func serializeGender(_ gender: SecureIdGender) -> String {
switch gender {
case .male:
return "male"
case .female:
return "female"
}
}
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
private func parseDate(_ string: String) -> SecureIdDate? {
guard let date = dateFormatter.date(from: string) else {
return nil
}
return SecureIdDate(timestamp: Int32(date.timeIntervalSince1970))
}
private func serializeDate(_ date: SecureIdDate) -> String {
return dateFormatter.string(from: Date(timeIntervalSince1970: Double(date.timestamp)))
}
private func parseFileReferenceId(_ string: String) -> Int64? {
let data = dataWithHexString(string)
if data.count != 8 {
return nil
}
var value: Int64 = 0
data.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
memcpy(&value, bytes, 8)
}
return value
}
private func serializeFileReferenceId(_ id: Int64) -> String {
var data = Data(count: 8)
data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
var id = id
memcpy(bytes, &id, 8)
}
return hexString(data)
}
extension SecureIdIdentityValue {
init?(data: Data, fileReferences: [Int64: SecureIdFileReference]) {
guard let dict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else {
return nil
}
guard let documentType = dict["document_type"] as? String else {
return nil
}
switch documentType {
case "passport":
if let passport = SecureIdIdentityPassportValue(dict: dict, fileReferences: fileReferences) {
self = .passport(passport)
}
default:
return nil
}
return nil
}
func serialize() -> (Data, [SecureIdFileReference])? {
var dict: [String: Any] = [:]
let fileReferences: [SecureIdFileReference]
switch self {
case let .passport(value):
dict["document_type"] = "passport"
let (valueDict, references) = value.serialize()
dict.merge(valueDict, uniquingKeysWith: { lhs, _ in return lhs })
fileReferences = references
}
guard let data = try? JSONSerialization.data(withJSONObject: dict, options: []) else {
return nil
}
return (data, fileReferences)
}
}
private extension SecureIdIdentityPassportValue {
init?(dict: [String: Any], fileReferences: [Int64: SecureIdFileReference]) {
guard let identifier = dict["document_no"] as? String else {
return nil
}
guard let firstName = dict["first_name"] as? String else {
return nil
}
guard let lastName = dict["last_name"] as? String else {
return nil
}
guard let birthdate = (dict["date_of_birth"] as? String).flatMap(parseDate) else {
return nil
}
guard let gender = (dict["gender"] as? String).flatMap(parseGender) else {
return nil
}
guard let countryCode = dict["country_code"] as? String else {
return nil
}
guard let issueDate = (dict["issue_date"] as? String).flatMap(parseDate) else {
return nil
}
let expiryDate = (dict["expiry_date"] as? String).flatMap(parseDate)
var verificationDocuments: [SecureIdFileReference] = []
if let files = dict["files"] as? [String] {
for fileId in files {
guard let fileId = parseFileReferenceId(fileId) else {
continue
}
guard let file = fileReferences[fileId] else {
continue
}
verificationDocuments.append(file)
}
}
self.init(identifier: identifier, firstName: firstName, lastName: lastName, birthdate: birthdate, countryCode: countryCode, gender: gender, issueDate: issueDate, expiryDate: expiryDate, verificationDocuments: verificationDocuments)
}
func serialize() -> ([String: Any], [SecureIdFileReference]) {
var dict: [String: Any] = [:]
dict["document_no"] = self.identifier
dict["first_name"] = self.firstName
dict["last_name"] = self.lastName
dict["date_of_birth"] = serializeDate(self.birthdate)
dict["gender"] = serializeGender(self.gender)
dict["country_code"] = self.countryCode
dict["issue_date"] = serializeDate(self.issueDate)
if let expiryDate = self.expiryDate {
dict["expiry_date"] = serializeDate(expiryDate)
}
if !self.verificationDocuments.isEmpty {
dict["files"] = self.verificationDocuments.map { serializeFileReferenceId($0.id) }
}
return (dict, self.verificationDocuments)
}
}

View File

@@ -1,41 +0,0 @@
import Foundation
public struct SecureIdPassportIdentity: Equatable {
public var id: String
public var firstName: String
public var lastName: String
public var birthdate: SecureIdDate
public var countryCode: String
public var gender: SecureIdGender
public init(id: String, firstName: String, lastName: String, birthdate: SecureIdDate, countryCode: String, gender: SecureIdGender) {
self.id = id
self.firstName = firstName
self.lastName = lastName
self.birthdate = birthdate
self.countryCode = countryCode
self.gender = gender
}
public static func ==(lhs: SecureIdPassportIdentity, rhs: SecureIdPassportIdentity) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.firstName != rhs.firstName {
return false
}
if lhs.lastName != rhs.lastName {
return false
}
if lhs.birthdate != rhs.birthdate {
return false
}
if lhs.countryCode != rhs.countryCode {
return false
}
if lhs.gender != rhs.gender {
return false
}
return true
}
}

View File

@@ -1,16 +0,0 @@
import Foundation
public struct SecureIdPhoneField: Equatable {
public let rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
public static func ==(lhs: SecureIdPhoneField, rhs: SecureIdPhoneField) -> Bool {
if lhs.rawValue != rhs.rawValue {
return false
}
return true
}
}

View File

@@ -0,0 +1,16 @@
import Foundation
public struct SecureIdPhoneValue: Equatable {
public let phone: String
public init(phone: String) {
self.phone = phone
}
public static func ==(lhs: SecureIdPhoneValue, rhs: SecureIdPhoneValue) -> Bool {
if lhs.phone != rhs.phone {
return false
}
return true
}
}

View File

@@ -19,22 +19,37 @@ public func twoStepVerificationConfiguration(account: Account) -> Signal<TwoStep
|> retryRequest
|> map { result -> TwoStepVerificationConfiguration in
switch result {
case let .noPassword(_, _, emailUnconfirmedPattern):
case let .noPassword(_, _, _, emailUnconfirmedPattern):
return .notSet(pendingEmailPattern: emailUnconfirmedPattern)
case let .password(_, _, _, hint, hasRecovery, emailUnconfirmedPattern):
case let .password(_, _, _, _, hint, hasRecovery, emailUnconfirmedPattern):
return .set(hint: hint, hasRecoveryEmail: hasRecovery == .boolTrue, pendingEmailPattern: emailUnconfirmedPattern)
}
}
}
public struct TwoStepVerificationSecureSecret {
public let data: Data
public let salt: Data
public let hash: Int64
}
public struct TwoStepVerificationSettings {
public let email: String
public let secureSecret: Data?
public let secureSecret: TwoStepVerificationSecureSecret?
}
public func requestTwoStepVerifiationSettings(network: Network, password: String) -> Signal<TwoStepVerificationSettings, AuthorizationPasswordVerificationError> {
return twoStepAuthData(network)
|> mapToSignal { authData -> Signal<TwoStepVerificationSettings, MTRpcError> in
|> mapError { error -> AuthorizationPasswordVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .invalidPassword
} else {
return .generic
}
}
|> mapToSignal { authData -> Signal<TwoStepVerificationSettings, AuthorizationPasswordVerificationError> in
var data = Data()
data.append(authData.currentSalt!)
data.append(password.data(using: .utf8, allowLossyConversion: true)!)
@@ -42,21 +57,22 @@ public func requestTwoStepVerifiationSettings(network: Network, password: String
let currentPasswordHash = sha256Digest(data)
return network.request(Api.functions.account.getPasswordSettings(currentPasswordHash: Buffer(data: currentPasswordHash)), automaticFloodWait: false)
|> map { result -> TwoStepVerificationSettings in
|> mapError { _ -> AuthorizationPasswordVerificationError in
return .generic
}
|> mapToSignal { result -> Signal<TwoStepVerificationSettings, AuthorizationPasswordVerificationError> in
switch result {
case let .passwordSettings(email, secureSecret):
return TwoStepVerificationSettings(email: email, secureSecret: secureSecret.size == 0 ? nil : secureSecret.makeData())
}
}
}
|> `catch` { error -> Signal<TwoStepVerificationSettings, AuthorizationPasswordVerificationError> in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .fail(.invalidPassword)
} else {
case let .passwordSettings(email, secureSalt, secureSecret, secureSecretHash):
var parsedSecureSecret: TwoStepVerificationSecureSecret?
if secureSalt.size != 0 && secureSecret.size != 0 {
if secureSecret.size != 32 {
return .fail(.generic)
}
parsedSecureSecret = TwoStepVerificationSecureSecret(data: secureSecret.makeData(), salt: secureSalt.makeData(), hash: secureSecretHash)
}
return .single(TwoStepVerificationSettings(email: email, secureSecret: parsedSecureSecret))
}
}
}
}
@@ -80,7 +96,7 @@ public func updateTwoStepVerificationPassword(network: Network, currentPassword:
|> mapError { _ -> UpdateTwoStepVerificationPasswordError in
return .generic
}
|> mapToSignal { authData -> Signal<(TwoStepAuthData, Data?), UpdateTwoStepVerificationPasswordError> in
|> mapToSignal { authData -> Signal<(TwoStepAuthData, TwoStepVerificationSecureSecret?), UpdateTwoStepVerificationPasswordError> in
if authData.currentSalt != nil {
return requestTwoStepVerifiationSettings(network: network, password: currentPassword ?? "")
|> mapError { _ -> UpdateTwoStepVerificationPasswordError in
@@ -114,7 +130,7 @@ public func updateTwoStepVerificationPassword(network: Network, currentPassword:
flags |= (1 << 0)
}
return network.request(Api.functions.account.updatePasswordSettings(currentPasswordHash: currentPasswordHash, newSettings: .passwordInputSettings(flags: flags, newSalt: Buffer(data: Data()), newPasswordHash: Buffer(data: Data()), hint: "", email: "", newSecureSecret: nil)), automaticFloodWait: false)
return network.request(Api.functions.account.updatePasswordSettings(currentPasswordHash: currentPasswordHash, newSettings: .passwordInputSettings(flags: flags, newSalt: Buffer(data: Data()), newPasswordHash: Buffer(data: Data()), hint: "", email: "", newSecureSalt: nil, newSecureSecret: nil, newSecureSecretHash: nil)), automaticFloodWait: false)
|> mapError { _ -> UpdateTwoStepVerificationPasswordError in
return .generic
}
@@ -140,18 +156,22 @@ public func updateTwoStepVerificationPassword(network: Network, currentPassword:
updatedData.append(password.data(using: .utf8, allowLossyConversion: true)!)
updatedData.append(nextSalt)
var updatedSecureSecret: Data?
var updatedSecureSecret: TwoStepVerificationSecureSecret?
if let encryptedSecret = secureSecret {
flags |= 1 << 2
if let decryptedSecret = decryptedSecureSecret(encryptedSecretData: encryptedSecret, password: currentPassword ?? "") {
updatedSecureSecret = encryptedSecureSecret(secretData: decryptedSecret, password: password)
if let decryptedSecret = decryptedSecureSecret(encryptedSecretData: encryptedSecret.data, password: currentPassword ?? "", salt: encryptedSecret.salt, hash: encryptedSecret.hash) {
if let (data, salt, hash) = encryptedSecureSecret(secretData: decryptedSecret, password: password, inputSalt: authData.nextSecureSalt) {
updatedSecureSecret = TwoStepVerificationSecureSecret(data: data, salt: salt, hash: hash)
} else {
updatedSecureSecret = Data()
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
let updatedPasswordHash = sha256Digest(updatedData)
return network.request(Api.functions.account.updatePasswordSettings(currentPasswordHash: currentPasswordHash, newSettings: Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newSalt: Buffer(data: nextSalt), newPasswordHash: Buffer(data: updatedPasswordHash), hint: hint, email: email, newSecureSecret: updatedSecureSecret.flatMap(Buffer.init))), automaticFloodWait: false)
return network.request(Api.functions.account.updatePasswordSettings(currentPasswordHash: currentPasswordHash, newSettings: Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newSalt: Buffer(data: nextSalt), newPasswordHash: Buffer(data: updatedPasswordHash), hint: hint, email: email, newSecureSalt: (updatedSecureSecret?.salt).flatMap(Buffer.init), newSecureSecret: (updatedSecureSecret?.data).flatMap(Buffer.init), newSecureSecretHash: updatedSecureSecret?.hash)), automaticFloodWait: false)
|> map { _ -> UpdateTwoStepVerificationPasswordResult in
return .password(password: password, pendingEmailPattern: nil)
}
@@ -184,7 +204,7 @@ enum UpdateTwoStepVerificationSecureSecretError {
case generic
}
func updateTwoStepVerificationSecureSecret(network: Network, password: String, updatedSecret: Data) -> Signal<UpdateTwoStepVerificationSecureSecretResult, UpdateTwoStepVerificationSecureSecretError> {
func updateTwoStepVerificationSecureSecret(network: Network, password: String, secret: Data) -> Signal<UpdateTwoStepVerificationSecureSecretResult, UpdateTwoStepVerificationSecureSecretError> {
return twoStepAuthData(network)
|> mapError { _ -> UpdateTwoStepVerificationSecureSecretError in
return .generic
@@ -194,14 +214,22 @@ func updateTwoStepVerificationSecureSecret(network: Network, password: String, u
return .fail(.generic)
}
guard let passwordData = password.data(using: .utf8) else {
return .fail(.generic)
}
var data = Data()
data.append(currentSalt)
data.append(password.data(using: .utf8, allowLossyConversion: true)!)
data.append(passwordData)
data.append(currentSalt)
let currentPasswordHash = Buffer(data: sha256Digest(data))
guard let (encryptedSecret, secretSalt, secretHash) = encryptedSecureSecret(secretData: secret, password: password, inputSalt: authData.nextSecureSalt) else {
return .fail(.generic)
}
let flags: Int32 = (1 << 2)
return network.request(Api.functions.account.updatePasswordSettings(currentPasswordHash: currentPasswordHash, newSettings: .passwordInputSettings(flags: flags, newSalt: nil, newPasswordHash: nil, hint: nil, email: nil, newSecureSecret: Buffer(data: updatedSecret))), automaticFloodWait: false)
return network.request(Api.functions.account.updatePasswordSettings(currentPasswordHash: currentPasswordHash, newSettings: .passwordInputSettings(flags: flags, newSalt: nil, newPasswordHash: nil, hint: nil, email: nil, newSecureSalt: Buffer(data: secretSalt), newSecureSecret: Buffer(data: encryptedSecret), newSecureSecretHash: secretHash)), automaticFloodWait: false)
|> mapError { _ -> UpdateTwoStepVerificationSecureSecretError in
return .generic
}
@@ -229,7 +257,7 @@ public func updateTwoStepVerificationEmail(account: Account, currentPassword: St
}
let flags: Int32 = 1 << 1
return account.network.request(Api.functions.account.updatePasswordSettings(currentPasswordHash: currentPasswordHash, newSettings: Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newSalt: nil, newPasswordHash: nil, hint: nil, email: updatedEmail, newSecureSecret: nil)), automaticFloodWait: false)
return account.network.request(Api.functions.account.updatePasswordSettings(currentPasswordHash: currentPasswordHash, newSettings: Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newSalt: nil, newPasswordHash: nil, hint: nil, email: updatedEmail, newSecureSalt: nil, newSecureSecret: nil, newSecureSecretHash: nil)), automaticFloodWait: false)
|> map { _ -> UpdateTwoStepVerificationPasswordResult in
return .password(password: currentPassword, pendingEmailPattern: nil)
}