Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,195 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import CryptoUtils
private enum GenerateSecureSecretError {
case generic
}
func encryptSecureData(key: Data, iv: Data, data: Data, decrypt: Bool) -> Data? {
if data.count % 16 != 0 {
return nil
}
return CryptoAES(!decrypt, key, iv, data)
}
func verifySecureSecret(_ data: Data) -> Bool {
guard data.withUnsafeBytes({ rawBytes -> Bool in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
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, derivation: TwoStepSecurePasswordDerivation, id: Int64) -> Data? {
guard let passwordHash = securePasswordKDF(password: password, derivation: derivation) else {
return nil
}
let secretKey = passwordHash.subdata(in: 0 ..< 32)
let iv = passwordHash.subdata(in: 32 ..< (32 + 16))
guard let decryptedSecret = CryptoAES(false, secretKey, iv, encryptedSecretData) else {
return nil
}
if !verifySecureSecret(decryptedSecret) {
return nil
}
let secretHashData = sha256Digest(decryptedSecret)
var secretId: Int64 = 0
secretHashData.withUnsafeBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: Int8.self)
memcpy(&secretId, bytes, 8)
}
if secretId != id {
return nil
}
return decryptedSecret
}
func encryptedSecureSecret(secretData: Data, password: String, inputDerivation: TwoStepSecurePasswordDerivation) -> (data: Data, salt: TwoStepSecurePasswordDerivation, id: Int64)? {
let secretHashData = sha256Digest(secretData)
var secretId: Int64 = 0
secretHashData.withUnsafeBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: Int8.self)
memcpy(&secretId, bytes, 8)
}
guard let (passwordHash, updatedDerivation) = securePasswordUpdateKDF(password: password, derivation: inputDerivation) else {
return nil
}
let secretKey = passwordHash.subdata(in: 0 ..< 32)
let iv = passwordHash.subdata(in: 32 ..< (32 + 16))
guard let encryptedSecret = CryptoAES(true, secretKey, iv, secretData) else {
return nil
}
if decryptedSecureSecret(encryptedSecretData: encryptedSecret, password: password, derivation: updatedDerivation, id: secretId) != secretData {
return nil
}
return (encryptedSecret, updatedDerivation, secretId)
}
func generateSecureSecretData() -> Data? {
var secretData = Data(count: 32)
let secretDataCount = secretData.count
guard secretData.withUnsafeMutableBytes({ rawBytes -> Bool in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: Int8.self)
let copyResult = SecRandomCopyBytes(nil, 32, bytes)
return copyResult == errSecSuccess
}) else {
return nil
}
secretData.withUnsafeMutableBytes({ rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
while true {
var checksum: UInt32 = 0
for i in 0 ..< secretDataCount {
checksum += UInt32(bytes.advanced(by: i).pointee)
checksum = checksum % 255
}
if checksum == 239 {
break
} else {
var i = secretDataCount - 1
inner: while i >= 0 {
var byte = bytes.advanced(by: i).pointee
if byte != 0xff {
byte += 1
bytes.advanced(by: i).pointee = byte
break inner
} else {
byte = 0
bytes.advanced(by: i).pointee = byte
}
i -= 1
}
}
}
})
return secretData
}
private func generateSecureSecret(network: Network, password: String) -> Signal<Data, GenerateSecureSecretError> {
guard let secretData = generateSecureSecretData() else {
return .fail(.generic)
}
return updateTwoStepVerificationSecureSecret(network: network, password: password, secret: secretData)
|> mapError { _ -> GenerateSecureSecretError in
return .generic
}
|> map { _ -> Data in
return secretData
}
}
public struct SecureIdAccessContext: Equatable {
let secret: Data
let id: Int64
}
public enum SecureIdAccessError {
case generic
case passwordError(AuthorizationPasswordVerificationError)
case secretPasswordMismatch
}
func _internal_accessSecureId(network: Network, password: String) -> Signal<(context: SecureIdAccessContext, settings: TwoStepVerificationSettings), SecureIdAccessError> {
return _internal_requestTwoStepVerifiationSettings(network: network, password: password)
|> mapError { error -> SecureIdAccessError in
return .passwordError(error)
}
|> mapToSignal { settings -> Signal<(context: SecureIdAccessContext, settings: TwoStepVerificationSettings), SecureIdAccessError> in
if let secureSecret = settings.secureSecret {
if let decryptedSecret = decryptedSecureSecret(encryptedSecretData: secureSecret.data, password: password, derivation: secureSecret.derivation, id: secureSecret.id) {
return .single((SecureIdAccessContext(secret: decryptedSecret, id: secureSecret.id), settings))
} else {
return .fail(.secretPasswordMismatch)
}
} else {
return generateSecureSecret(network: network, password: password)
|> mapError { _ -> SecureIdAccessError in
return SecureIdAccessError.generic
}
|> map { decryptedSecret in
let secretHashData = sha256Digest(decryptedSecret)
var secretId: Int64 = 0
secretHashData.withUnsafeBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: Int8.self)
memcpy(&secretId, bytes, 8)
}
return (SecureIdAccessContext(secret: decryptedSecret, id: secretId), settings)
}
}
}
}
@@ -0,0 +1,342 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
func apiSecureValueType(value: SecureIdValue) -> Api.SecureValueType {
let type: Api.SecureValueType
switch value {
case .personalDetails:
type = .secureValueTypePersonalDetails
case .passport:
type = .secureValueTypePassport
case .internalPassport:
type = .secureValueTypeInternalPassport
case .driversLicense:
type = .secureValueTypeDriverLicense
case .idCard:
type = .secureValueTypeIdentityCard
case .address:
type = .secureValueTypeAddress
case .passportRegistration:
type = .secureValueTypePassportRegistration
case .temporaryRegistration:
type = .secureValueTypeTemporaryRegistration
case .bankStatement:
type = .secureValueTypeBankStatement
case .utilityBill:
type = .secureValueTypeUtilityBill
case .rentalAgreement:
type = .secureValueTypeRentalAgreement
case .phone:
type = .secureValueTypePhone
case .email:
type = .secureValueTypeEmail
}
return type
}
func apiSecureValueType(key: SecureIdValueKey) -> Api.SecureValueType {
let type: Api.SecureValueType
switch key {
case .personalDetails:
type = .secureValueTypePersonalDetails
case .passport:
type = .secureValueTypePassport
case .internalPassport:
type = .secureValueTypeInternalPassport
case .driversLicense:
type = .secureValueTypeDriverLicense
case .idCard:
type = .secureValueTypeIdentityCard
case .address:
type = .secureValueTypeAddress
case .passportRegistration:
type = .secureValueTypePassportRegistration
case .temporaryRegistration:
type = .secureValueTypeTemporaryRegistration
case .bankStatement:
type = .secureValueTypeBankStatement
case .utilityBill:
type = .secureValueTypeUtilityBill
case .rentalAgreement:
type = .secureValueTypeRentalAgreement
case .phone:
type = .secureValueTypePhone
case .email:
type = .secureValueTypeEmail
}
return type
}
extension SecureIdValueKey {
init(apiType: Api.SecureValueType) {
switch apiType {
case .secureValueTypePersonalDetails:
self = .personalDetails
case .secureValueTypePassport:
self = .passport
case .secureValueTypeDriverLicense:
self = .driversLicense
case .secureValueTypeIdentityCard:
self = .idCard
case .secureValueTypeInternalPassport:
self = .internalPassport
case .secureValueTypeAddress:
self = .address
case .secureValueTypeUtilityBill:
self = .utilityBill
case .secureValueTypeBankStatement:
self = .bankStatement
case .secureValueTypeRentalAgreement:
self = .rentalAgreement
case .secureValueTypePassportRegistration:
self = .passportRegistration
case .secureValueTypeTemporaryRegistration:
self = .temporaryRegistration
case .secureValueTypePhone:
self = .phone
case .secureValueTypeEmail:
self = .email
}
}
}
private func credentialsValueTypeName(value: SecureIdValue) -> String {
switch value {
case .personalDetails:
return "personal_details"
case .passport:
return "passport"
case .internalPassport:
return "internal_passport"
case .driversLicense:
return "driver_license"
case .idCard:
return "identity_card"
case .address:
return "address"
case .passportRegistration:
return "passport_registration"
case .temporaryRegistration:
return "temporary_registration"
case .bankStatement:
return "bank_statement"
case .utilityBill:
return "utility_bill"
case .rentalAgreement:
return "rental_agreement"
case .phone:
return "phone"
case .email:
return "email"
}
}
private func generateCredentials(values: [SecureIdValueWithContext], requestedFields: [SecureIdRequestedFormField], opaquePayload: Data, opaqueNonce: Data) -> Data? {
var secureData: [String: Any] = [:]
let requestedFieldValues = requestedFields.flatMap({ field -> [SecureIdRequestedFormFieldValue] in
switch field {
case let .just(value):
return [value]
case let .oneOf(values):
return values
}
})
let valueTypeToSkipFields: [SecureIdValueKey: (SecureIdRequestedFormFieldValue) -> (needsSelfie: Bool, needsTranslations: Bool)?] = [
.idCard: {
if case let .idCard(selfie, translations) = $0 {
return (selfie, translations)
} else {
return nil
}
},
.passport: {
if case let .passport(selfie, translations) = $0 {
return (selfie, translations)
} else {
return nil
}
},
.driversLicense: {
if case let .driversLicense(selfie, translations) = $0 {
return (selfie, translations)
} else {
return nil
}
},
.internalPassport: {
if case let .internalPassport(selfie, translations) = $0 {
return (selfie, translations)
} else {
return nil
}
},
.passportRegistration: {
if case let .passportRegistration(translations) = $0 {
return (false, translations)
} else {
return nil
}
},
.temporaryRegistration: {
if case let .temporaryRegistration(translations) = $0 {
return (false, translations)
} else {
return nil
}
},
.utilityBill: {
if case let .utilityBill(translations) = $0 {
return (false, translations)
} else {
return nil
}
},
.bankStatement: {
if case let .bankStatement(translations) = $0 {
return (false, translations)
} else {
return nil
}
},
.rentalAgreement: {
if case let .rentalAgreement(translations) = $0 {
return (false, translations)
} else {
return nil
}
}
]
for value in values {
var skipSelfie = false
var skipTranslations = false
if let skipFilter = valueTypeToSkipFields[value.value.key] {
inner: for field in requestedFieldValues {
if let result = skipFilter(field) {
skipSelfie = !result.needsSelfie
skipTranslations = !result.needsTranslations
break inner
}
}
}
var valueDict: [String: Any] = [:]
if let encryptedMetadata = value.encryptedMetadata {
valueDict["data"] = [
"data_hash": encryptedMetadata.valueDataHash.base64EncodedString(),
"secret": encryptedMetadata.decryptedSecret.base64EncodedString()
] as [String: Any]
}
if !value.files.isEmpty {
valueDict["files"] = value.files.map { file -> [String: Any] in
return [
"file_hash": file.hash.base64EncodedString(),
"secret": file.secret.base64EncodedString()
]
}
}
if !skipTranslations && !value.translations.isEmpty {
valueDict["translation"] = value.translations.map { file -> [String: Any] in
return [
"file_hash": file.hash.base64EncodedString(),
"secret": file.secret.base64EncodedString()
]
}
}
if !skipSelfie, let selfie = value.selfie {
valueDict["selfie"] = [
"file_hash": selfie.hash.base64EncodedString(),
"secret": selfie.secret.base64EncodedString()
] as [String: Any]
}
if let frontside = value.frontSide {
valueDict["front_side"] = [
"file_hash": frontside.hash.base64EncodedString(),
"secret": frontside.secret.base64EncodedString()
] as [String: Any]
}
if let backside = value.backSide {
valueDict["reverse_side"] = [
"file_hash": backside.hash.base64EncodedString(),
"secret": backside.secret.base64EncodedString()
] as [String: Any]
}
if !valueDict.isEmpty {
secureData[credentialsValueTypeName(value: value.value)] = valueDict
}
}
var dict: [String: Any] = [:]
dict["secure_data"] = secureData
if !opaquePayload.isEmpty, let opaquePayload = String(data: opaquePayload, encoding: .utf8) {
dict["payload"] = opaquePayload
}
if !opaqueNonce.isEmpty, let opaqueNonce = String(data: opaqueNonce, encoding: .utf8) {
dict["nonce"] = opaqueNonce
}
guard let data = try? JSONSerialization.data(withJSONObject: dict, options: []) else {
return nil
}
return data
}
private func encryptedCredentialsData(data: Data, secretData: Data) -> (data: Data, hash: Data)? {
let paddedData = paddedSecureIdData(data)
let hash = sha256Digest(paddedData)
let secretHash = sha512Digest(secretData + hash)
let key = secretHash.subdata(in: 0 ..< 32)
let iv = secretHash.subdata(in: 32 ..< (32 + 16))
guard let encryptedData = encryptSecureData(key: key, iv: iv, data: paddedData, decrypt: false) else {
return nil
}
return (encryptedData, hash)
}
public enum GrantSecureIdAccessError {
case generic
}
public func grantSecureIdAccess(network: Network, peerId: PeerId, publicKey: String, scope: String, opaquePayload: Data, opaqueNonce: Data, values: [SecureIdValueWithContext], requestedFields: [SecureIdRequestedFormField]) -> Signal<Void, GrantSecureIdAccessError> {
guard peerId.namespace == Namespaces.Peer.CloudUser else {
return .fail(.generic)
}
guard let credentialsSecretData = generateSecureSecretData() else {
return .fail(.generic)
}
guard let credentialsData = generateCredentials(values: values, requestedFields: requestedFields, opaquePayload: opaquePayload, opaqueNonce: opaqueNonce) else {
return .fail(.generic)
}
guard let (encryptedCredentialsData, decryptedCredentialsHash) = encryptedCredentialsData(data: credentialsData, secretData: credentialsSecretData) else {
return .fail(.generic)
}
guard let encryptedSecretData = MTRsaEncryptPKCS1OAEP(network.encryptionProvider, publicKey, credentialsSecretData) else {
return .fail(.generic)
}
var valueHashes: [Api.SecureValueHash] = []
for value in values {
valueHashes.append(.secureValueHash(type: apiSecureValueType(value: value.value), hash: Buffer(data: value.opaqueHash)))
}
return network.request(Api.functions.account.acceptAuthorization(botId: peerId.id._internalGetInt64Value(), scope: scope, publicKey: publicKey, valueHashes: valueHashes, credentials: .secureCredentialsEncrypted(data: Buffer(data: encryptedCredentialsData), hash: Buffer(data: decryptedCredentialsHash), secret: Buffer(data: encryptedSecretData))))
|> mapError { error -> GrantSecureIdAccessError in
return .generic
}
|> mapToSignal { _ -> Signal<Void, GrantSecureIdAccessError> in
return .complete()
}
}
@@ -0,0 +1,291 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public enum RequestSecureIdFormError {
case generic
case serverError(String)
case versionOutdated
}
private func parseSecureValueType(_ type: Api.SecureValueType, selfie: Bool, translation: Bool, nativeNames: Bool) -> SecureIdRequestedFormFieldValue {
switch type {
case .secureValueTypePersonalDetails:
return .personalDetails(nativeName: nativeNames)
case .secureValueTypePassport:
return .passport(selfie: selfie, translation: translation)
case .secureValueTypeInternalPassport:
return .internalPassport(selfie: selfie, translation: translation)
case .secureValueTypeDriverLicense:
return .driversLicense(selfie: selfie, translation: translation)
case .secureValueTypeIdentityCard:
return .idCard(selfie: selfie, translation: translation)
case .secureValueTypeAddress:
return .address
case .secureValueTypeUtilityBill:
return .utilityBill(translation: translation)
case .secureValueTypeBankStatement:
return .bankStatement(translation: translation)
case .secureValueTypeRentalAgreement:
return .rentalAgreement(translation: translation)
case .secureValueTypePhone:
return .phone
case .secureValueTypeEmail:
return .email
case .secureValueTypePassportRegistration:
return .passportRegistration(translation: translation)
case .secureValueTypeTemporaryRegistration:
return .temporaryRegistration(translation: translation)
}
}
private func parseSecureData(_ value: Api.SecureData) -> (data: Data, hash: Data, secret: Data) {
switch value {
case let .secureData(data, dataHash, secret):
return (data.makeData(), dataHash.makeData(), secret.makeData())
}
}
struct ParsedSecureValue {
let valueWithContext: SecureIdValueWithContext
}
func parseSecureValue(context: SecureIdAccessContext, value: Api.SecureValue, errors: [Api.SecureValueError]) -> ParsedSecureValue? {
switch value {
case let .secureValue(_, type, data, frontSide, reverseSide, selfie, translation, files, plainData, hash):
let parsedFileReferences = files.flatMap { $0.compactMap(SecureIdFileReference.init) } ?? []
let parsedFiles = parsedFileReferences.map(SecureIdVerificationDocumentReference.remote)
let parsedTranslationReferences = translation.flatMap { $0.compactMap(SecureIdFileReference.init) } ?? []
let parsedTranslations = parsedTranslationReferences.map(SecureIdVerificationDocumentReference.remote)
let parsedFrontSide = frontSide.flatMap(SecureIdFileReference.init).flatMap(SecureIdVerificationDocumentReference.remote)
let parsedBackSide = reverseSide.flatMap(SecureIdFileReference.init).flatMap(SecureIdVerificationDocumentReference.remote)
let parsedSelfie = selfie.flatMap(SecureIdFileReference.init).flatMap(SecureIdVerificationDocumentReference.remote)
let decryptedData: Data?
let encryptedMetadata: SecureIdEncryptedValueMetadata?
var parsedFileMetadata: [SecureIdEncryptedValueFileMetadata] = []
var parsedTranslationMetadata: [SecureIdEncryptedValueFileMetadata] = []
var parsedSelfieMetadata: SecureIdEncryptedValueFileMetadata?
var parsedFrontSideMetadata: SecureIdEncryptedValueFileMetadata?
var parsedBackSideMetadata: SecureIdEncryptedValueFileMetadata?
var contentsId: Data?
if let data = data {
let (encryptedData, decryptedHash, encryptedSecret) = parseSecureData(data)
guard let valueContext = decryptedSecureValueAccessContext(context: context, encryptedSecret: encryptedSecret, decryptedDataHash: decryptedHash) else {
return nil
}
contentsId = decryptedHash
decryptedData = decryptedSecureValueData(context: valueContext, encryptedData: encryptedData, decryptedDataHash: decryptedHash)
if decryptedData == nil {
return nil
}
encryptedMetadata = SecureIdEncryptedValueMetadata(valueDataHash: decryptedHash, decryptedSecret: valueContext.secret)
} else {
decryptedData = nil
encryptedMetadata = nil
}
for file in parsedFileReferences {
guard let fileSecret = decryptedSecureIdFileSecret(context: context, fileHash: file.fileHash, encryptedSecret: file.encryptedSecret) else {
return nil
}
parsedFileMetadata.append(SecureIdEncryptedValueFileMetadata(hash: file.fileHash, secret: fileSecret))
}
for file in parsedTranslationReferences {
guard let fileSecret = decryptedSecureIdFileSecret(context: context, fileHash: file.fileHash, encryptedSecret: file.encryptedSecret) else {
return nil
}
parsedTranslationMetadata.append(SecureIdEncryptedValueFileMetadata(hash: file.fileHash, secret: fileSecret))
}
if let parsedSelfie = selfie.flatMap(SecureIdFileReference.init) {
guard let fileSecret = decryptedSecureIdFileSecret(context: context, fileHash: parsedSelfie.fileHash, encryptedSecret: parsedSelfie.encryptedSecret) else {
return nil
}
parsedSelfieMetadata = SecureIdEncryptedValueFileMetadata(hash: parsedSelfie.fileHash, secret: fileSecret)
}
if let parsedFrontSide = frontSide.flatMap(SecureIdFileReference.init) {
guard let fileSecret = decryptedSecureIdFileSecret(context: context, fileHash: parsedFrontSide.fileHash, encryptedSecret: parsedFrontSide.encryptedSecret) else {
return nil
}
parsedFrontSideMetadata = SecureIdEncryptedValueFileMetadata(hash: parsedFrontSide.fileHash, secret: fileSecret)
}
if let parsedBackSide = reverseSide.flatMap(SecureIdFileReference.init) {
guard let fileSecret = decryptedSecureIdFileSecret(context: context, fileHash: parsedBackSide.fileHash, encryptedSecret: parsedBackSide.encryptedSecret) else {
return nil
}
parsedBackSideMetadata = SecureIdEncryptedValueFileMetadata(hash: parsedBackSide.fileHash, secret: fileSecret)
}
let value: SecureIdValue
switch type {
case .secureValueTypePersonalDetails:
guard let dict = (try? JSONSerialization.jsonObject(with: decryptedData ?? Data(), options: [])) as? [String: Any] else {
return nil
}
guard let personalDetails = SecureIdPersonalDetailsValue(dict: dict, fileReferences: parsedFiles) else {
return nil
}
value = .personalDetails(personalDetails)
case .secureValueTypePassport:
guard let dict = (try? JSONSerialization.jsonObject(with: decryptedData ?? Data(), options: [])) as? [String: Any] else {
return nil
}
guard let passport = SecureIdPassportValue(dict: dict, fileReferences: parsedFiles, translations: parsedTranslations, selfieDocument: parsedSelfie, frontSideDocument: parsedFrontSide) else {
return nil
}
value = .passport(passport)
case .secureValueTypeInternalPassport:
guard let dict = (try? JSONSerialization.jsonObject(with: decryptedData ?? Data(), options: [])) as? [String: Any] else {
return nil
}
guard let internalPassport = SecureIdInternalPassportValue(dict: dict, fileReferences: parsedFiles, translations: parsedTranslations, selfieDocument: parsedSelfie, frontSideDocument: parsedFrontSide) else {
return nil
}
value = .internalPassport(internalPassport)
case .secureValueTypeDriverLicense:
guard let dict = (try? JSONSerialization.jsonObject(with: decryptedData ?? Data(), options: [])) as? [String: Any] else {
return nil
}
guard let driversLicense = SecureIdDriversLicenseValue(dict: dict, fileReferences: parsedFiles, translations: parsedTranslations, selfieDocument: parsedSelfie, frontSideDocument: parsedFrontSide, backSideDocument: parsedBackSide) else {
return nil
}
value = .driversLicense(driversLicense)
case .secureValueTypeIdentityCard:
guard let dict = (try? JSONSerialization.jsonObject(with: decryptedData ?? Data(), options: [])) as? [String: Any] else {
return nil
}
guard let idCard = SecureIdIDCardValue(dict: dict, fileReferences: parsedFiles, translations: parsedTranslations, selfieDocument: parsedSelfie, frontSideDocument: parsedFrontSide, backSideDocument: parsedBackSide) else {
return nil
}
value = .idCard(idCard)
case .secureValueTypeAddress:
guard let dict = (try? JSONSerialization.jsonObject(with: decryptedData ?? Data(), options: [])) as? [String: Any] else {
return nil
}
guard let address = SecureIdAddressValue(dict: dict, fileReferences: parsedFiles) else {
return nil
}
value = .address(address)
case .secureValueTypePassportRegistration:
guard let passportRegistration = SecureIdPassportRegistrationValue(fileReferences: parsedFiles, translations: parsedTranslations) else {
return nil
}
value = .passportRegistration(passportRegistration)
case .secureValueTypeTemporaryRegistration:
guard let temporaryRegistration = SecureIdTemporaryRegistrationValue(fileReferences: parsedFiles, translations: parsedTranslations) else {
return nil
}
value = .temporaryRegistration(temporaryRegistration)
case .secureValueTypeUtilityBill:
guard let utilityBill = SecureIdUtilityBillValue(fileReferences: parsedFiles, translations: parsedTranslations) else {
return nil
}
value = .utilityBill(utilityBill)
case .secureValueTypeBankStatement:
guard let bankStatement = SecureIdBankStatementValue(fileReferences: parsedFiles, translations: parsedTranslations) else {
return nil
}
value = .bankStatement(bankStatement)
case .secureValueTypeRentalAgreement:
guard let rentalAgreement = SecureIdRentalAgreementValue(fileReferences: parsedFiles, translations: parsedTranslations) else {
return nil
}
value = .rentalAgreement(rentalAgreement)
case .secureValueTypePhone:
guard let publicData = plainData else {
return nil
}
switch publicData {
case let .securePlainPhone(phone):
value = .phone(SecureIdPhoneValue(phone: phone))
default:
return nil
}
case .secureValueTypeEmail:
guard let publicData = plainData else {
return nil
}
switch publicData {
case let .securePlainEmail(email):
value = .email(SecureIdEmailValue(email: email))
default:
return nil
}
}
return ParsedSecureValue(valueWithContext: SecureIdValueWithContext(value: value, errors: parseSecureIdValueContentErrors(dataHash: contentsId, fileHashes: Set(parsedFileMetadata.map { $0.hash } + parsedTranslationMetadata.map { $0.hash}), selfieHash: parsedSelfieMetadata?.hash, frontSideHash: parsedFrontSideMetadata?.hash, backSideHash: parsedBackSideMetadata?.hash, errors: errors), files: parsedFileMetadata, translations: parsedTranslationMetadata, selfie: parsedSelfieMetadata, frontSide: parsedFrontSideMetadata, backSide: parsedBackSideMetadata, encryptedMetadata: encryptedMetadata, opaqueHash: hash.makeData()))
}
}
private func parseSecureValues(context: SecureIdAccessContext, values: [Api.SecureValue], errors: [Api.SecureValueError], requestedFields: [SecureIdRequestedFormField]) -> [SecureIdValueWithContext] {
return values.map({ apiValue in
return parseSecureValue(context: context, value: apiValue, errors: errors)
}).compactMap({ $0?.valueWithContext })
}
public struct EncryptedSecureIdForm {
public let peerId: PeerId
public let requestedFields: [SecureIdRequestedFormField]
public let termsUrl: String?
let encryptedValues: [Api.SecureValue]
let errors: [Api.SecureValueError]
}
public func requestSecureIdForm(accountPeerId: PeerId, postbox: Postbox, network: Network, peerId: PeerId, scope: String, publicKey: String) -> Signal<EncryptedSecureIdForm, RequestSecureIdFormError> {
if peerId.namespace != Namespaces.Peer.CloudUser {
return .fail(.serverError("BOT_INVALID"))
}
if scope.isEmpty {
return .fail(.serverError("SCOPE_EMPTY"))
}
if publicKey.isEmpty {
return .fail(.serverError("PUBLIC_KEY_REQUIRED"))
}
return network.request(Api.functions.account.getAuthorizationForm(botId: peerId.id._internalGetInt64Value(), scope: scope, publicKey: publicKey))
|> mapError { error -> RequestSecureIdFormError in
switch error.errorDescription {
case "APP_VERSION_OUTDATED":
return .versionOutdated
default:
return .serverError(error.errorDescription)
}
}
|> mapToSignal { result -> Signal<EncryptedSecureIdForm, RequestSecureIdFormError> in
return postbox.transaction { transaction -> EncryptedSecureIdForm in
switch result {
case let .authorizationForm(_, requiredTypes, values, errors, users, termsUrl):
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users))
return EncryptedSecureIdForm(peerId: peerId, requestedFields: requiredTypes.map { requiredType in
switch requiredType {
case let .secureRequiredType(flags, type):
return .just(parseSecureValueType(type, selfie: (flags & 1 << 1) != 0, translation: (flags & 1 << 2) != 0, nativeNames: (flags & 1 << 0) != 0))
case let .secureRequiredTypeOneOf(types):
let parsedInnerTypes = types.compactMap { innerType -> SecureIdRequestedFormFieldValue? in
switch innerType {
case let .secureRequiredType(flags, type):
return parseSecureValueType(type, selfie: (flags & 1 << 1) != 0, translation: (flags & 1 << 2) != 0, nativeNames: (flags & 1 << 0) != 0)
case .secureRequiredTypeOneOf:
return nil
}
}
return .oneOf(parsedInnerTypes)
}
}, termsUrl: termsUrl, encryptedValues: values, errors: errors)
}
} |> mapError { _ -> RequestSecureIdFormError in }
}
}
public func decryptedSecureIdForm(context: SecureIdAccessContext, form: EncryptedSecureIdForm) -> SecureIdForm? {
return SecureIdForm(peerId: form.peerId, requestedFields: form.requestedFields, values: parseSecureValues(context: context, values: form.encryptedValues, errors: form.errors, requestedFields: form.requestedFields))
}
@@ -0,0 +1,350 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public enum SaveSecureIdValueError {
case generic
case verificationRequired
case versionOutdated
}
struct EncryptedSecureData {
let data: Data
let dataHash: Data
let encryptedSecret: Data
}
func encryptedSecureValueData(context: SecureIdAccessContext, valueContext: SecureIdValueAccessContext, data: Data) -> EncryptedSecureData? {
let valueData = paddedSecureIdData(data)
let valueHash = sha256Digest(valueData)
let valueSecretHash = sha512Digest(valueContext.secret + valueHash)
let valueKey = valueSecretHash.subdata(in: 0 ..< 32)
let valueIv = valueSecretHash.subdata(in: 32 ..< (32 + 16))
guard let encryptedValueData = encryptSecureData(key: valueKey, iv: valueIv, data: valueData, decrypt: false) else {
return nil
}
let secretHash = sha512Digest(context.secret + valueHash)
let secretKey = secretHash.subdata(in: 0 ..< 32)
let secretIv = secretHash.subdata(in: 32 ..< (32 + 16))
guard let encryptedValueSecret = encryptSecureData(key: secretKey, iv: secretIv, data: valueContext.secret, decrypt: false) else {
return nil
}
return EncryptedSecureData(data: encryptedValueData, dataHash: valueHash, encryptedSecret: encryptedValueSecret)
}
func decryptedSecureValueAccessContext(context: SecureIdAccessContext, encryptedSecret: Data, decryptedDataHash: Data) -> SecureIdValueAccessContext? {
let secretHash = sha512Digest(context.secret + decryptedDataHash)
let secretKey = secretHash.subdata(in: 0 ..< 32)
let secretIv = secretHash.subdata(in: 32 ..< (32 + 16))
guard let valueSecret = encryptSecureData(key: secretKey, iv: secretIv, data: encryptedSecret, decrypt: true) else {
return nil
}
if !verifySecureSecret(valueSecret) {
return nil
}
let valueSecretHash = sha512Digest(valueSecret)
var valueSecretIdValue: Int64 = 0
valueSecretHash.withUnsafeBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: Int8.self)
memcpy(&valueSecretIdValue, bytes.advanced(by: valueSecretHash.count - 8), 8)
}
return SecureIdValueAccessContext(secret: valueSecret, id: valueSecretIdValue)
}
func decryptedSecureValueData(context: SecureIdValueAccessContext, encryptedData: Data, decryptedDataHash: Data) -> Data? {
let valueSecretHash = sha512Digest(context.secret + decryptedDataHash)
let valueKey = valueSecretHash.subdata(in: 0 ..< 32)
let valueIv = valueSecretHash.subdata(in: 32 ..< (32 + 16))
guard let decryptedValueData = encryptSecureData(key: valueKey, iv: valueIv, data: encryptedData, decrypt: true) else {
return nil
}
let checkDataHash = sha256Digest(decryptedValueData)
if checkDataHash != decryptedDataHash {
return nil
}
guard let unpaddedValueData = unpaddedSecureIdData(decryptedValueData) else {
return nil
}
return unpaddedValueData
}
private func apiInputSecretFile(_ file: SecureIdVerificationDocumentReference) -> Api.InputSecureFile {
switch file {
case let .remote(file):
return Api.InputSecureFile.inputSecureFile(id: file.id, accessHash: file.accessHash)
case let .uploaded(file):
return Api.InputSecureFile.inputSecureFileUploaded(id: file.id, parts: file.parts, md5Checksum: file.md5Checksum, fileHash: Buffer(data: file.fileHash), secret: Buffer(data: file.encryptedSecret))
}
}
private struct InputSecureIdValueData {
let type: Api.SecureValueType
let dict: [String: Any]?
let fileReferences: [SecureIdVerificationDocumentReference]
let translationReferences: [SecureIdVerificationDocumentReference]
let frontSideReference: SecureIdVerificationDocumentReference?
let backSideReference: SecureIdVerificationDocumentReference?
let selfieReference: SecureIdVerificationDocumentReference?
let publicData: Api.SecurePlainData?
}
private func inputSecureIdValueData(value: SecureIdValue) -> InputSecureIdValueData {
switch value {
case let .personalDetails(personalDetails):
let (dict, fileReferences) = personalDetails.serialize()
return InputSecureIdValueData(type: .secureValueTypePersonalDetails, dict: dict, fileReferences: fileReferences, translationReferences: [], frontSideReference: nil, backSideReference: nil, selfieReference: nil, publicData: nil)
case let .passport(passport):
let (dict, fileReferences, translationReferences, selfieReference, frontSideReference) = passport.serialize()
return InputSecureIdValueData(type: .secureValueTypePassport, dict: dict, fileReferences: fileReferences, translationReferences: translationReferences, frontSideReference: frontSideReference, backSideReference: nil, selfieReference: selfieReference, publicData: nil)
case let .internalPassport(internalPassport):
let (dict, fileReferences, translationReferences, selfieReference, frontSideReference) = internalPassport.serialize()
return InputSecureIdValueData(type: .secureValueTypeInternalPassport, dict: dict, fileReferences: fileReferences, translationReferences: translationReferences, frontSideReference: frontSideReference, backSideReference: nil, selfieReference: selfieReference, publicData: nil)
case let .driversLicense(driversLicense):
let (dict, fileReferences, translationReferences, selfieReference, frontSideReference, backSideReference) = driversLicense.serialize()
return InputSecureIdValueData(type: .secureValueTypeDriverLicense, dict: dict, fileReferences: fileReferences, translationReferences: translationReferences, frontSideReference: frontSideReference, backSideReference: backSideReference, selfieReference: selfieReference, publicData: nil)
case let .idCard(idCard):
let (dict, fileReferences, translationReferences, selfieReference, frontSideReference, backSideReference) = idCard.serialize()
return InputSecureIdValueData(type: .secureValueTypeIdentityCard, dict: dict, fileReferences: fileReferences, translationReferences: translationReferences, frontSideReference: frontSideReference, backSideReference: backSideReference, selfieReference: selfieReference, publicData: nil)
case let .address(address):
let (dict, fileReferences) = address.serialize()
return InputSecureIdValueData(type: .secureValueTypeAddress, dict: dict, fileReferences: fileReferences, translationReferences: [], frontSideReference: nil, backSideReference: nil, selfieReference: nil, publicData: nil)
case let .passportRegistration(passportRegistration):
let (dict, fileReferences, translations) = passportRegistration.serialize()
return InputSecureIdValueData(type: .secureValueTypePassportRegistration, dict: dict, fileReferences: fileReferences, translationReferences: translations, frontSideReference: nil, backSideReference: nil, selfieReference: nil, publicData: nil)
case let .temporaryRegistration(temporaryRegistration):
let (dict, fileReferences, translations) = temporaryRegistration.serialize()
return InputSecureIdValueData(type: .secureValueTypeTemporaryRegistration, dict: dict, fileReferences: fileReferences, translationReferences: translations, frontSideReference: nil, backSideReference: nil, selfieReference: nil, publicData: nil)
case let .utilityBill(utilityBill):
let (dict, fileReferences, translations) = utilityBill.serialize()
return InputSecureIdValueData(type: .secureValueTypeUtilityBill, dict: dict, fileReferences: fileReferences, translationReferences: translations, frontSideReference: nil, backSideReference: nil, selfieReference: nil, publicData: nil)
case let .bankStatement(bankStatement):
let (dict, fileReferences, translations) = bankStatement.serialize()
return InputSecureIdValueData(type: .secureValueTypeBankStatement, dict: dict, fileReferences: fileReferences, translationReferences: translations, frontSideReference: nil, backSideReference: nil, selfieReference: nil, publicData: nil)
case let .rentalAgreement(rentalAgreement):
let (dict, fileReferences, translations) = rentalAgreement.serialize()
return InputSecureIdValueData(type: .secureValueTypeRentalAgreement, dict: dict, fileReferences: fileReferences, translationReferences: translations, frontSideReference: nil, backSideReference: nil, selfieReference: nil, publicData: nil)
case let .phone(phone):
return InputSecureIdValueData(type: .secureValueTypePhone, dict: nil, fileReferences: [], translationReferences: [], frontSideReference: nil, backSideReference: nil, selfieReference: nil, publicData: .securePlainPhone(phone: phone.phone))
case let .email(email):
return InputSecureIdValueData(type: .secureValueTypeEmail, dict: nil, fileReferences: [], translationReferences: [], frontSideReference: nil, backSideReference: nil, selfieReference: nil, publicData: .securePlainEmail(email: email.email))
}
}
private func makeInputSecureValue(context: SecureIdAccessContext, value: SecureIdValue) -> Api.InputSecureValue? {
let inputData = inputSecureIdValueData(value: value)
var secureData: Api.SecureData?
if let dict = inputData.dict {
guard let decryptedData = try? JSONSerialization.data(withJSONObject: dict, options: []) else {
return nil
}
guard let valueContext = generateSecureIdValueAccessContext() else {
return nil
}
guard let encryptedData = encryptedSecureValueData(context: context, valueContext: valueContext, data: decryptedData) else {
return nil
}
guard let checkValueContext = decryptedSecureValueAccessContext(context: context, encryptedSecret: encryptedData.encryptedSecret, decryptedDataHash: encryptedData.dataHash) else {
return nil
}
if checkValueContext != valueContext {
return nil
}
if let checkData = decryptedSecureValueData(context: checkValueContext, encryptedData: encryptedData.data, decryptedDataHash: encryptedData.dataHash) {
if checkData != decryptedData {
return nil
}
} else {
return nil
}
secureData = .secureData(data: Buffer(data: encryptedData.data), dataHash: Buffer(data: encryptedData.dataHash), secret: Buffer(data: encryptedData.encryptedSecret))
}
var flags: Int32 = 0
let files = inputData.fileReferences.map(apiInputSecretFile)
let translations = inputData.translationReferences.map(apiInputSecretFile)
if secureData != nil {
flags |= 1 << 0
}
if inputData.frontSideReference != nil {
flags |= 1 << 1
}
if inputData.backSideReference != nil {
flags |= 1 << 2
}
if inputData.selfieReference != nil {
flags |= 1 << 3
}
if !files.isEmpty {
flags |= 1 << 4
}
if !translations.isEmpty {
flags |= 1 << 6
}
if inputData.publicData != nil {
flags |= 1 << 5
}
return Api.InputSecureValue.inputSecureValue(flags: flags, type: inputData.type, data: secureData, frontSide: inputData.frontSideReference.flatMap(apiInputSecretFile), reverseSide: inputData.backSideReference.flatMap(apiInputSecretFile), selfie: inputData.selfieReference.flatMap(apiInputSecretFile), translation: translations, files: files, plainData: inputData.publicData)
}
public func saveSecureIdValue(postbox: Postbox, network: Network, context: SecureIdAccessContext, value: SecureIdValue, uploadedFiles: [Data: Data]) -> Signal<SecureIdValueWithContext, SaveSecureIdValueError> {
let delete = deleteSecureIdValues(network: network, keys: Set([value.key]))
|> mapError { _ -> SaveSecureIdValueError in
return .generic
}
|> mapToSignal { _ -> Signal<SecureIdValueWithContext, SaveSecureIdValueError> in
return .complete()
}
|> `catch` { _ -> Signal<SecureIdValueWithContext, SaveSecureIdValueError> in
return .complete()
}
guard let inputValue = makeInputSecureValue(context: context, value: value) else {
return .fail(.generic)
}
let save = network.request(Api.functions.account.saveSecureValue(value: inputValue, secureSecretId: context.id))
|> mapError { error -> SaveSecureIdValueError in
switch error.errorDescription {
case "PHONE_VERIFICATION_NEEDED", "EMAIL_VERIFICATION_NEEDED":
return .verificationRequired
case "APP_VERSION_OUTDATED":
return .versionOutdated
default:
return .generic
}
}
|> mapToSignal { result -> Signal<SecureIdValueWithContext, SaveSecureIdValueError> in
guard let parsedValue = parseSecureValue(context: context, value: result, errors: []) else {
return .fail(.generic)
}
for file in parsedValue.valueWithContext.value.fileReferences {
switch file {
case let .remote(file):
if let data = uploadedFiles[file.fileHash] {
postbox.mediaBox.storeResourceData(SecureFileMediaResource(file: file).id, data: data)
}
case .uploaded:
break
}
}
return .single(parsedValue.valueWithContext)
}
return delete |> then(save)
}
public enum DeleteSecureIdValueError {
case generic
case versionOutdated
}
public func deleteSecureIdValues(network: Network, keys: Set<SecureIdValueKey>) -> Signal<Void, DeleteSecureIdValueError> {
return network.request(Api.functions.account.deleteSecureValue(types: keys.map(apiSecureValueType(key:))))
|> mapError { error -> DeleteSecureIdValueError in
switch error.errorDescription {
case "APP_VERSION_OUTDATED":
return .versionOutdated
default:
return .generic
}
}
|> mapToSignal { _ -> Signal<Void, DeleteSecureIdValueError> in
return .complete()
}
}
public func dropSecureId(network: Network, currentPassword: String) -> Signal<Void, AuthorizationPasswordVerificationError> {
return _internal_twoStepAuthData(network)
|> mapError { _ -> AuthorizationPasswordVerificationError in
return .generic
}
|> mapToSignal { authData -> Signal<Void, AuthorizationPasswordVerificationError> in
let checkPassword: Api.InputCheckPasswordSRP
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
let kdfResult = passwordKDF(encryptionProvider: network.encryptionProvider, password: currentPassword, derivation: currentPasswordDerivation, srpSessionData: srpSessionData)
if let kdfResult = kdfResult {
checkPassword = .inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1))
} else {
return .fail(.generic)
}
} else {
checkPassword = .inputCheckPasswordEmpty
}
let settings = network.request(Api.functions.account.getPasswordSettings(password: checkPassword), automaticFloodWait: false)
|> mapError { error in
return AuthorizationPasswordVerificationError.generic
}
return settings
|> mapToSignal { value -> Signal<Void, AuthorizationPasswordVerificationError> in
switch value {
case .passwordSettings:
var flags: Int32 = 0
flags |= (1 << 2)
return network.request(Api.functions.account.updatePasswordSettings(password: .inputCheckPasswordEmpty, newSettings: .passwordInputSettings(flags: flags, newAlgo: nil, newPasswordHash: nil, hint: nil, email: nil, newSecureSettings: nil)), automaticFloodWait: false)
|> map { _ in }
|> mapError { _ in
return AuthorizationPasswordVerificationError.generic
}
}
}
}
}
public enum GetAllSecureIdValuesError {
case generic
case versionOutdated
}
public struct EncryptedAllSecureIdValues {
fileprivate let values: [Api.SecureValue]
}
public func getAllSecureIdValues(network: Network) -> Signal<EncryptedAllSecureIdValues, GetAllSecureIdValuesError> {
return network.request(Api.functions.account.getAllSecureValues())
|> mapError { error -> GetAllSecureIdValuesError in
switch error.errorDescription {
case "APP_VERSION_OUTDATED":
return .versionOutdated
default:
return .generic
}
}
|> map { result in
return EncryptedAllSecureIdValues(values: result)
}
}
public func decryptedAllSecureIdValues(context: SecureIdAccessContext, encryptedValues: EncryptedAllSecureIdValues) -> [SecureIdValueWithContext] {
var values: [SecureIdValueWithContext] = []
for value in encryptedValues.values {
if let parsedValue = parseSecureValue(context: context, value: value, errors: []) {
values.append(parsedValue.valueWithContext)
}
}
return values
}
@@ -0,0 +1,17 @@
import Foundation
import Postbox
import TelegramApi
extension SecureFileMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource, EncryptedMediaResource {
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
return Api.InputFileLocation.inputSecureFileLocation(id: self.file.id, accessHash: self.file.accessHash)
}
public func decrypt(data: Data, params: Any) -> Data? {
guard let context = params as? SecureIdAccessContext else {
return nil
}
return decryptedSecureIdFile(context: context, encryptedData: data, fileHash: self.file.fileHash, encryptedSecret: self.file.encryptedSecret)
}
}
@@ -0,0 +1,78 @@
import Foundation
public struct SecureIdAddressValue: Equatable {
public var street1: String
public var street2: String
public var city: String
public var state: String
public var countryCode: String
public var postcode: String
public init(street1: String, street2: String, city: String, state: String, countryCode: String, postcode: String) {
self.street1 = street1
self.street2 = street2
self.city = city
self.state = state
self.countryCode = countryCode
self.postcode = postcode
}
public static func ==(lhs: SecureIdAddressValue, rhs: SecureIdAddressValue) -> Bool {
if lhs.street1 != rhs.street1 {
return false
}
if lhs.street2 != rhs.street2 {
return false
}
if lhs.city != rhs.city {
return false
}
if lhs.state != rhs.state {
return false
}
if lhs.countryCode != rhs.countryCode {
return false
}
if lhs.postcode != rhs.postcode {
return false
}
return true
}
}
extension SecureIdAddressValue {
init?(dict: [String: Any], fileReferences: [SecureIdVerificationDocumentReference]) {
guard let street1 = dict["street_line1"] as? String else {
return nil
}
let street2 = (dict["street_line2"] as? String) ?? ""
guard let city = dict["city"] as? String else {
return nil
}
guard let state = dict["state"] as? String else {
return nil
}
guard let countryCode = dict["country_code"] as? String else {
return nil
}
guard let postcode = dict["post_code"] as? String else {
return nil
}
self.init(street1: street1, street2: street2, city: city, state: state, countryCode: countryCode, postcode: postcode)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference]) {
var dict: [String: Any] = [:]
dict["street_line1"] = self.street1
if !self.street2.isEmpty {
dict["street_line2"] = self.street2
}
dict["city"] = self.city
dict["state"] = self.state
dict["country_code"] = self.countryCode
dict["post_code"] = self.postcode
return (dict, [])
}
}
@@ -0,0 +1,33 @@
import Foundation
public struct SecureIdBankStatementValue: Equatable {
public var verificationDocuments: [SecureIdVerificationDocumentReference]
public var translations: [SecureIdVerificationDocumentReference]
public init(verificationDocuments: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference]) {
self.verificationDocuments = verificationDocuments
self.translations = translations
}
public static func ==(lhs: SecureIdBankStatementValue, rhs: SecureIdBankStatementValue) -> Bool {
if lhs.verificationDocuments != rhs.verificationDocuments {
return false
}
if lhs.translations != rhs.translations {
return false
}
return true
}
}
extension SecureIdBankStatementValue {
init?(fileReferences: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference]) {
let verificationDocuments: [SecureIdVerificationDocumentReference] = fileReferences
self.init(verificationDocuments: verificationDocuments, translations: translations)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference], [SecureIdVerificationDocumentReference]) {
return ([:], self.verificationDocuments, self.translations)
}
}
@@ -0,0 +1,45 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public func secureIdConfiguration(postbox: Postbox, network: Network) -> Signal<SecureIdConfiguration, NoError> {
let cached: Signal<CachedSecureIdConfiguration?, NoError> = postbox.transaction { transaction -> CachedSecureIdConfiguration? in
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedSecureIdConfiguration, key: ValueBoxKey(length: 0)))?.get(CachedSecureIdConfiguration.self) {
return entry
} else {
return nil
}
}
return cached
|> mapToSignal { cached -> Signal<SecureIdConfiguration, NoError> in
return network.request(Api.functions.help.getPassportConfig(hash: cached?.hash ?? 0))
|> retryRequest
|> mapToSignal { result -> Signal<SecureIdConfiguration, NoError> in
let parsed: CachedSecureIdConfiguration
switch result {
case .passportConfigNotModified:
if let cached = cached {
return .single(cached.value)
} else {
assertionFailure()
return .complete()
}
case let .passportConfig(hash, countriesLangs):
switch countriesLangs {
case let .dataJSON(data):
let value = SecureIdConfiguration(jsonString: data)
parsed = CachedSecureIdConfiguration(value: value, hash: hash)
}
}
return postbox.transaction { transaction -> SecureIdConfiguration in
if let entry = CodableEntry(parsed) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedSecureIdConfiguration, key: ValueBoxKey(length: 0)), entry: entry)
}
return parsed.value
}
}
}
}
@@ -0,0 +1,86 @@
import Foundation
import TelegramApi
public struct SecureIdPersonName: Equatable {
public let firstName: String
public let lastName: String
public let middleName: String
public init(firstName: String, lastName: String, middleName: String) {
self.firstName = firstName
self.lastName = lastName
self.middleName = middleName
}
public func isComplete() -> Bool {
return !self.firstName.isEmpty && !self.lastName.isEmpty
}
}
public struct SecureIdDate: Equatable {
public let day: Int32
public let month: Int32
public let year: Int32
public init(day: Int32, month: Int32, year: Int32) {
self.day = day
self.month = month
self.year = year
}
}
public enum SecureIdGender {
case male
case female
}
extension SecureIdFileReference {
init?(apiFile: Api.SecureFile) {
switch apiFile {
case let .secureFile(id, accessHash, size, dcId, date, fileHash, secret):
self.init(id: id, accessHash: accessHash, size: size, datacenterId: dcId, timestamp: date, fileHash: fileHash.makeData(), encryptedSecret: secret.makeData())
case .secureFileEmpty:
return nil
}
}
}
extension SecureIdGender {
init?(serializedString: String) {
switch serializedString {
case "male":
self = .male
case "female":
self = .female
default:
return nil
}
}
func serialize() -> String {
switch self {
case .male:
return "male"
case .female:
return "female"
}
}
}
extension SecureIdDate {
init?(serializedString: String) {
let data = serializedString.components(separatedBy: ".")
guard data.count == 3 else {
return nil
}
guard let day = Int32(data[0]), let month = Int32(data[1]), let year = Int32(data[2]) else {
return nil
}
self.init(day: day, month: month, year: year)
}
func serialize() -> String {
return "\(self.day < 10 ? "0\(self.day)" : "\(self.day)").\(self.month < 10 ? "0\(self.month)" : "\(self.month)").\(self.year)"
}
}
@@ -0,0 +1,69 @@
import Foundation
public struct SecureIdDriversLicenseValue: Equatable {
public var identifier: String
public var expiryDate: SecureIdDate?
public var verificationDocuments: [SecureIdVerificationDocumentReference]
public var translations: [SecureIdVerificationDocumentReference]
public var selfieDocument: SecureIdVerificationDocumentReference?
public var frontSideDocument: SecureIdVerificationDocumentReference?
public var backSideDocument: SecureIdVerificationDocumentReference?
public init(identifier: String, expiryDate: SecureIdDate?, verificationDocuments: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference], selfieDocument: SecureIdVerificationDocumentReference?, frontSideDocument: SecureIdVerificationDocumentReference?, backSideDocument: SecureIdVerificationDocumentReference?) {
self.identifier = identifier
self.expiryDate = expiryDate
self.verificationDocuments = verificationDocuments
self.translations = translations
self.selfieDocument = selfieDocument
self.frontSideDocument = frontSideDocument
self.backSideDocument = backSideDocument
}
public static func ==(lhs: SecureIdDriversLicenseValue, rhs: SecureIdDriversLicenseValue) -> Bool {
if lhs.identifier != rhs.identifier {
return false
}
if lhs.expiryDate != rhs.expiryDate {
return false
}
if lhs.verificationDocuments != rhs.verificationDocuments {
return false
}
if lhs.translations != rhs.translations {
return false
}
if lhs.selfieDocument != rhs.selfieDocument {
return false
}
if lhs.frontSideDocument != rhs.frontSideDocument {
return false
}
if lhs.backSideDocument != rhs.backSideDocument {
return false
}
return true
}
}
extension SecureIdDriversLicenseValue {
init?(dict: [String: Any], fileReferences: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference], selfieDocument: SecureIdVerificationDocumentReference?, frontSideDocument: SecureIdVerificationDocumentReference?, backSideDocument: SecureIdVerificationDocumentReference?) {
guard let identifier = dict["document_no"] as? String else {
return nil
}
let expiryDate = (dict["expiry_date"] as? String).flatMap(SecureIdDate.init)
let verificationDocuments: [SecureIdVerificationDocumentReference] = fileReferences
self.init(identifier: identifier, expiryDate: expiryDate, verificationDocuments: verificationDocuments, translations: translations, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument, backSideDocument: backSideDocument)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference], [SecureIdVerificationDocumentReference], SecureIdVerificationDocumentReference?, SecureIdVerificationDocumentReference?, SecureIdVerificationDocumentReference?) {
var dict: [String: Any] = [:]
dict["document_no"] = self.identifier
if let expiryDate = self.expiryDate {
dict["expiry_date"] = expiryDate.serialize()
}
return (dict, self.verificationDocuments, self.translations, self.selfieDocument, self.frontSideDocument, self.backSideDocument)
}
}
@@ -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
}
}
@@ -0,0 +1,48 @@
import Foundation
import Postbox
public enum SecureIdRequestedFormField: Equatable {
case just(SecureIdRequestedFormFieldValue)
case oneOf([SecureIdRequestedFormFieldValue])
}
public enum SecureIdRequestedFormFieldValue: Equatable {
case personalDetails(nativeName: Bool)
case passport(selfie: Bool, translation: Bool)
case driversLicense(selfie: Bool, translation: Bool)
case idCard(selfie: Bool, translation: Bool)
case internalPassport(selfie: Bool, translation: Bool)
case passportRegistration(translation: Bool)
case address
case utilityBill(translation: Bool)
case bankStatement(translation: Bool)
case rentalAgreement(translation: Bool)
case phone
case email
case temporaryRegistration(translation: Bool)
}
public struct SecureIdForm: Equatable {
public let peerId: PeerId
public let requestedFields: [SecureIdRequestedFormField]
public let values: [SecureIdValueWithContext]
public init(peerId: PeerId, requestedFields: [SecureIdRequestedFormField], values: [SecureIdValueWithContext]) {
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
}
}
@@ -0,0 +1,69 @@
import Foundation
public struct SecureIdIDCardValue: Equatable {
public var identifier: String
public var expiryDate: SecureIdDate?
public var verificationDocuments: [SecureIdVerificationDocumentReference]
public var translations: [SecureIdVerificationDocumentReference]
public var selfieDocument: SecureIdVerificationDocumentReference?
public var frontSideDocument: SecureIdVerificationDocumentReference?
public var backSideDocument: SecureIdVerificationDocumentReference?
public init(identifier: String, expiryDate: SecureIdDate?, verificationDocuments: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference], selfieDocument: SecureIdVerificationDocumentReference?, frontSideDocument: SecureIdVerificationDocumentReference?, backSideDocument: SecureIdVerificationDocumentReference?) {
self.identifier = identifier
self.expiryDate = expiryDate
self.verificationDocuments = verificationDocuments
self.translations = translations
self.selfieDocument = selfieDocument
self.frontSideDocument = frontSideDocument
self.backSideDocument = backSideDocument
}
public static func ==(lhs: SecureIdIDCardValue, rhs: SecureIdIDCardValue) -> Bool {
if lhs.identifier != rhs.identifier {
return false
}
if lhs.expiryDate != rhs.expiryDate {
return false
}
if lhs.verificationDocuments != rhs.verificationDocuments {
return false
}
if lhs.translations != rhs.translations {
return false
}
if lhs.selfieDocument != rhs.selfieDocument {
return false
}
if lhs.frontSideDocument != rhs.frontSideDocument {
return false
}
if lhs.backSideDocument != rhs.backSideDocument {
return false
}
return true
}
}
extension SecureIdIDCardValue {
init?(dict: [String: Any], fileReferences: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference], selfieDocument: SecureIdVerificationDocumentReference?, frontSideDocument: SecureIdVerificationDocumentReference?, backSideDocument: SecureIdVerificationDocumentReference?) {
guard let identifier = dict["document_no"] as? String else {
return nil
}
let expiryDate = (dict["expiry_date"] as? String).flatMap(SecureIdDate.init)
let verificationDocuments: [SecureIdVerificationDocumentReference] = fileReferences
self.init(identifier: identifier, expiryDate: expiryDate, verificationDocuments: verificationDocuments, translations: translations, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument, backSideDocument: backSideDocument)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference], [SecureIdVerificationDocumentReference], SecureIdVerificationDocumentReference?, SecureIdVerificationDocumentReference?, SecureIdVerificationDocumentReference?) {
var dict: [String: Any] = [:]
dict["document_no"] = self.identifier
if let expiryDate = self.expiryDate {
dict["expiry_date"] = expiryDate.serialize()
}
return (dict, self.verificationDocuments, self.translations, self.selfieDocument, self.frontSideDocument, self.backSideDocument)
}
}
@@ -0,0 +1,64 @@
import Foundation
public struct SecureIdInternalPassportValue: Equatable {
public var identifier: String
public var expiryDate: SecureIdDate?
public var verificationDocuments: [SecureIdVerificationDocumentReference]
public var translations: [SecureIdVerificationDocumentReference]
public var selfieDocument: SecureIdVerificationDocumentReference?
public var frontSideDocument: SecureIdVerificationDocumentReference?
public init(identifier: String, expiryDate: SecureIdDate?, verificationDocuments: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference], selfieDocument: SecureIdVerificationDocumentReference?, frontSideDocument: SecureIdVerificationDocumentReference?) {
self.identifier = identifier
self.expiryDate = expiryDate
self.verificationDocuments = verificationDocuments
self.translations = translations
self.selfieDocument = selfieDocument
self.frontSideDocument = frontSideDocument
}
public static func ==(lhs: SecureIdInternalPassportValue, rhs: SecureIdInternalPassportValue) -> Bool {
if lhs.identifier != rhs.identifier {
return false
}
if lhs.expiryDate != rhs.expiryDate {
return false
}
if lhs.verificationDocuments != rhs.verificationDocuments {
return false
}
if lhs.translations != rhs.translations {
return false
}
if lhs.selfieDocument != rhs.selfieDocument {
return false
}
if lhs.frontSideDocument != rhs.frontSideDocument {
return false
}
return true
}
}
extension SecureIdInternalPassportValue {
init?(dict: [String: Any], fileReferences: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference], selfieDocument: SecureIdVerificationDocumentReference?, frontSideDocument: SecureIdVerificationDocumentReference?) {
guard let identifier = dict["document_no"] as? String else {
return nil
}
let expiryDate = (dict["expiry_date"] as? String).flatMap(SecureIdDate.init)
let verificationDocuments: [SecureIdVerificationDocumentReference] = fileReferences
self.init(identifier: identifier, expiryDate: expiryDate, verificationDocuments: verificationDocuments, translations: translations, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference], [SecureIdVerificationDocumentReference], SecureIdVerificationDocumentReference?, SecureIdVerificationDocumentReference?) {
var dict: [String: Any] = [:]
dict["document_no"] = self.identifier
if let expiryDate = self.expiryDate {
dict["expiry_date"] = expiryDate.serialize()
}
return (dict, self.verificationDocuments, self.translations, self.selfieDocument, self.frontSideDocument)
}
}
@@ -0,0 +1,30 @@
import Foundation
func paddedSecureIdData(_ 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 { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
bytes.advanced(by: 0).pointee = UInt8(paddingCount)
arc4random_buf(bytes.advanced(by: 1), paddingCount - 1)
data.withUnsafeBytes { rawSource -> Void in
let source = rawSource.baseAddress!.assumingMemoryBound(to: UInt8.self)
memcpy(bytes.advanced(by: paddingCount), source, data.count)
}
}
return result
}
func unpaddedSecureIdData(_ 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)
}
@@ -0,0 +1,33 @@
import Foundation
public struct SecureIdPassportRegistrationValue: Equatable {
public var verificationDocuments: [SecureIdVerificationDocumentReference]
public var translations: [SecureIdVerificationDocumentReference]
public init(verificationDocuments: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference]) {
self.verificationDocuments = verificationDocuments
self.translations = translations
}
public static func ==(lhs: SecureIdPassportRegistrationValue, rhs: SecureIdPassportRegistrationValue) -> Bool {
if lhs.verificationDocuments != rhs.verificationDocuments {
return false
}
if lhs.translations != rhs.translations {
return false
}
return true
}
}
extension SecureIdPassportRegistrationValue {
init?(fileReferences: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference]) {
let verificationDocuments: [SecureIdVerificationDocumentReference] = fileReferences
self.init(verificationDocuments: verificationDocuments, translations: translations)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference], [SecureIdVerificationDocumentReference]) {
return ([:], self.verificationDocuments, self.translations)
}
}
@@ -0,0 +1,65 @@
import Foundation
public struct SecureIdPassportValue: Equatable {
public var identifier: String
public var expiryDate: SecureIdDate?
public var verificationDocuments: [SecureIdVerificationDocumentReference]
public var translations: [SecureIdVerificationDocumentReference]
public var selfieDocument: SecureIdVerificationDocumentReference?
public var frontSideDocument: SecureIdVerificationDocumentReference?
public init(identifier: String, expiryDate: SecureIdDate?, verificationDocuments: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference], selfieDocument: SecureIdVerificationDocumentReference?, frontSideDocument: SecureIdVerificationDocumentReference?) {
self.identifier = identifier
self.expiryDate = expiryDate
self.verificationDocuments = verificationDocuments
self.translations = translations
self.selfieDocument = selfieDocument
self.frontSideDocument = frontSideDocument
}
public static func ==(lhs: SecureIdPassportValue, rhs: SecureIdPassportValue) -> Bool {
if lhs.identifier != rhs.identifier {
return false
}
if lhs.expiryDate != rhs.expiryDate {
return false
}
if lhs.verificationDocuments != rhs.verificationDocuments {
return false
}
if lhs.translations != rhs.translations {
return false
}
if lhs.selfieDocument != rhs.selfieDocument {
return false
}
if lhs.frontSideDocument != rhs.frontSideDocument {
return false
}
return true
}
}
extension SecureIdPassportValue {
init?(dict: [String: Any], fileReferences: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference], selfieDocument: SecureIdVerificationDocumentReference?, frontSideDocument: SecureIdVerificationDocumentReference?) {
guard let identifier = dict["document_no"] as? String else {
return nil
}
let expiryDate = (dict["expiry_date"] as? String).flatMap(SecureIdDate.init)
let verificationDocuments: [SecureIdVerificationDocumentReference] = fileReferences
self.init(identifier: identifier, expiryDate: expiryDate, verificationDocuments: verificationDocuments, translations: translations, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference], [SecureIdVerificationDocumentReference], SecureIdVerificationDocumentReference?, SecureIdVerificationDocumentReference?) {
var dict: [String: Any] = [:]
dict["document_no"] = self.identifier
if let expiryDate = self.expiryDate {
dict["expiry_date"] = expiryDate.serialize()
}
return (dict, self.verificationDocuments,
self.translations, self.selfieDocument, self.frontSideDocument)
}
}
@@ -0,0 +1,73 @@
import Foundation
public struct SecureIdPersonalDetailsValue: Equatable {
public var latinName: SecureIdPersonName
public var nativeName: SecureIdPersonName?
public var birthdate: SecureIdDate
public var countryCode: String
public var residenceCountryCode: String
public var gender: SecureIdGender
public init(latinName: SecureIdPersonName, nativeName: SecureIdPersonName?, birthdate: SecureIdDate, countryCode: String, residenceCountryCode: String, gender: SecureIdGender) {
self.latinName = latinName
self.nativeName = nativeName
self.birthdate = birthdate
self.countryCode = countryCode
self.residenceCountryCode = residenceCountryCode
self.gender = gender
}
}
extension SecureIdPersonalDetailsValue {
init?(dict: [String: Any], fileReferences: [SecureIdVerificationDocumentReference]) {
guard let firstName = dict["first_name"] as? String else {
return nil
}
guard let lastName = dict["last_name"] as? String else {
return nil
}
let middleName = dict["middle_name"] as? String ?? ""
var nativeName: SecureIdPersonName?
if let nativeFirstName = dict["first_name_native"] as? String, let nativeLastName = dict["last_name_native"] as? String {
nativeName = SecureIdPersonName(firstName: nativeFirstName, lastName: nativeLastName, middleName: dict["middle_name_native"] as? String ?? "")
}
guard let birthdate = (dict["birth_date"] as? String).flatMap(SecureIdDate.init) else {
return nil
}
guard let gender = (dict["gender"] as? String).flatMap(SecureIdGender.init) else {
return nil
}
guard let countryCode = dict["country_code"] as? String else {
return nil
}
guard let residenceCountryCode = dict["residence_country_code"] as? String else {
return nil
}
self.init(latinName: SecureIdPersonName(firstName: firstName, lastName: lastName, middleName: middleName), nativeName: nativeName, birthdate: birthdate, countryCode: countryCode, residenceCountryCode: residenceCountryCode, gender: gender)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference]) {
var dict: [String: Any] = [:]
dict["first_name"] = self.latinName.firstName
if !self.latinName.middleName.isEmpty {
dict["middle_name"] = self.latinName.middleName
}
dict["last_name"] = self.latinName.lastName
if let nativeName = self.nativeName {
dict["first_name_native"] = nativeName.firstName
if !nativeName.middleName.isEmpty {
dict["middle_name_native"] = nativeName.middleName
}
dict["last_name_native"] = nativeName.lastName
}
dict["birth_date"] = self.birthdate.serialize()
dict["gender"] = self.gender.serialize()
dict["country_code"] = self.countryCode
dict["residence_country_code"] = self.residenceCountryCode
return (dict, [])
}
}
@@ -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
}
}
@@ -0,0 +1,33 @@
import Foundation
public struct SecureIdRentalAgreementValue: Equatable {
public var verificationDocuments: [SecureIdVerificationDocumentReference]
public var translations: [SecureIdVerificationDocumentReference]
public init(verificationDocuments: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference]) {
self.verificationDocuments = verificationDocuments
self.translations = translations
}
public static func ==(lhs: SecureIdRentalAgreementValue, rhs: SecureIdRentalAgreementValue) -> Bool {
if lhs.verificationDocuments != rhs.verificationDocuments {
return false
}
if lhs.translations != rhs.translations {
return false
}
return true
}
}
extension SecureIdRentalAgreementValue {
init?(fileReferences: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference]) {
let verificationDocuments: [SecureIdVerificationDocumentReference] = fileReferences
self.init(verificationDocuments: verificationDocuments, translations: translations)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference], [SecureIdVerificationDocumentReference]) {
return ([:], self.verificationDocuments, self.translations)
}
}
@@ -0,0 +1,33 @@
import Foundation
public struct SecureIdTemporaryRegistrationValue: Equatable {
public var verificationDocuments: [SecureIdVerificationDocumentReference]
public var translations: [SecureIdVerificationDocumentReference]
public init(verificationDocuments: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference]) {
self.verificationDocuments = verificationDocuments
self.translations = translations
}
public static func ==(lhs: SecureIdTemporaryRegistrationValue, rhs: SecureIdTemporaryRegistrationValue) -> Bool {
if lhs.verificationDocuments != rhs.verificationDocuments {
return false
}
if lhs.translations != rhs.translations {
return false
}
return true
}
}
extension SecureIdTemporaryRegistrationValue {
init?(fileReferences: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference]) {
let verificationDocuments: [SecureIdVerificationDocumentReference] = fileReferences
self.init(verificationDocuments: verificationDocuments, translations: translations)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference], [SecureIdVerificationDocumentReference]) {
return ([:], self.verificationDocuments, self.translations)
}
}
@@ -0,0 +1,33 @@
import Foundation
public struct SecureIdUtilityBillValue: Equatable {
public var verificationDocuments: [SecureIdVerificationDocumentReference]
public var translations: [SecureIdVerificationDocumentReference]
public init(verificationDocuments: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference]) {
self.verificationDocuments = verificationDocuments
self.translations = translations
}
public static func ==(lhs: SecureIdUtilityBillValue, rhs: SecureIdUtilityBillValue) -> Bool {
if lhs.verificationDocuments != rhs.verificationDocuments {
return false
}
if lhs.translations != rhs.translations {
return false
}
return true
}
}
extension SecureIdUtilityBillValue {
init?(fileReferences: [SecureIdVerificationDocumentReference], translations: [SecureIdVerificationDocumentReference]) {
let verificationDocuments: [SecureIdVerificationDocumentReference] = fileReferences
self.init(verificationDocuments: verificationDocuments, translations: translations)
}
func serialize() -> ([String: Any], [SecureIdVerificationDocumentReference], [SecureIdVerificationDocumentReference]) {
return ([:], self.verificationDocuments, self.translations)
}
}
@@ -0,0 +1,208 @@
import Foundation
public enum SecureIdValueKey: Int32 {
case personalDetails
case passport
case internalPassport
case driversLicense
case idCard
case address
case utilityBill
case bankStatement
case rentalAgreement
case passportRegistration
case temporaryRegistration
case phone
case email
}
public enum SecureIdValue: Equatable {
case personalDetails(SecureIdPersonalDetailsValue)
case passport(SecureIdPassportValue)
case internalPassport(SecureIdInternalPassportValue)
case driversLicense(SecureIdDriversLicenseValue)
case idCard(SecureIdIDCardValue)
case address(SecureIdAddressValue)
case passportRegistration(SecureIdPassportRegistrationValue)
case temporaryRegistration(SecureIdTemporaryRegistrationValue)
case utilityBill(SecureIdUtilityBillValue)
case bankStatement(SecureIdBankStatementValue)
case rentalAgreement(SecureIdRentalAgreementValue)
case phone(SecureIdPhoneValue)
case email(SecureIdEmailValue)
var fileReferences: [SecureIdVerificationDocumentReference] {
switch self {
case let .passport(passport):
var result = passport.verificationDocuments
if let selfie = passport.selfieDocument {
result.append(selfie)
}
if let frontSide = passport.frontSideDocument {
result.append(frontSide)
}
result.append(contentsOf: passport.translations)
return result
case let .internalPassport(passport):
var result = passport.verificationDocuments
if let selfie = passport.selfieDocument {
result.append(selfie)
}
if let frontSide = passport.frontSideDocument {
result.append(frontSide)
}
result.append(contentsOf: passport.translations)
return result
case let .driversLicense(driversLicense):
var result = driversLicense.verificationDocuments
if let selfie = driversLicense.selfieDocument {
result.append(selfie)
}
if let frontSide = driversLicense.frontSideDocument {
result.append(frontSide)
}
if let backSide = driversLicense.backSideDocument {
result.append(backSide)
}
result.append(contentsOf: driversLicense.translations)
return result
case let .idCard(idCard):
var result = idCard.verificationDocuments
if let selfie = idCard.selfieDocument {
result.append(selfie)
}
if let frontSide = idCard.frontSideDocument {
result.append(frontSide)
}
if let backSide = idCard.backSideDocument {
result.append(backSide)
}
result.append(contentsOf: idCard.translations)
return result
case let .passportRegistration(passportRegistration):
return passportRegistration.verificationDocuments + passportRegistration.translations
case let .temporaryRegistration(passportRegistration):
return passportRegistration.verificationDocuments + passportRegistration.translations
case let .bankStatement(bankStatement):
return bankStatement.verificationDocuments + bankStatement.translations
case let .utilityBill(utilityBill):
return utilityBill.verificationDocuments + utilityBill.translations
case let .rentalAgreement(rentalAgreement):
return rentalAgreement.verificationDocuments + rentalAgreement.translations
default:
return []
}
}
public var key: SecureIdValueKey {
switch self {
case .personalDetails:
return .personalDetails
case .passport:
return .passport
case .internalPassport:
return .internalPassport
case .driversLicense:
return .driversLicense
case .idCard:
return .idCard
case .address:
return .address
case .passportRegistration:
return .passportRegistration
case .temporaryRegistration:
return .temporaryRegistration
case .utilityBill:
return .utilityBill
case .bankStatement:
return .bankStatement
case .rentalAgreement:
return .rentalAgreement
case .phone:
return .phone
case .email:
return .email
}
}
}
public struct SecureIdValueAdditionalData {
public var nativeNames: Bool = false
public var selfie: Bool = false
public var translation: Bool = false
}
public func extractSecureIdValueAdditionalData(_ value: SecureIdValue) -> SecureIdValueAdditionalData {
var data = SecureIdValueAdditionalData()
switch value {
case let .personalDetails(value):
data.nativeNames = value.nativeName?.isComplete() ?? false
case let .passport(value):
data.selfie = value.selfieDocument != nil
data.translation = !value.translations.isEmpty
case let .internalPassport(value):
data.selfie = value.selfieDocument != nil
data.translation = !value.translations.isEmpty
case let .idCard(value):
data.selfie = value.selfieDocument != nil
data.translation = !value.translations.isEmpty
case let .driversLicense(value):
data.selfie = value.selfieDocument != nil
data.translation = !value.translations.isEmpty
case let .utilityBill(value):
data.translation = !value.translations.isEmpty
case let .rentalAgreement(value):
data.translation = !value.translations.isEmpty
case let .bankStatement(value):
data.translation = !value.translations.isEmpty
case let .temporaryRegistration(value):
data.translation = !value.translations.isEmpty
case let .passportRegistration(value):
data.translation = !value.translations.isEmpty
default:
break
}
return data
}
public struct SecureIdEncryptedValueFileMetadata: Equatable {
let hash: Data
let secret: Data
}
public struct SecureIdEncryptedValueMetadata: Equatable {
let valueDataHash: Data
let decryptedSecret: Data
}
public struct SecureIdValueWithContext: Equatable {
public let value: SecureIdValue
public let errors: [SecureIdValueContentErrorKey: SecureIdValueContentError]
let files: [SecureIdEncryptedValueFileMetadata]
let translations: [SecureIdEncryptedValueFileMetadata]
let selfie: SecureIdEncryptedValueFileMetadata?
let frontSide: SecureIdEncryptedValueFileMetadata?
let backSide: SecureIdEncryptedValueFileMetadata?
let encryptedMetadata: SecureIdEncryptedValueMetadata?
let opaqueHash: Data
init(value: SecureIdValue, errors: [SecureIdValueContentErrorKey: SecureIdValueContentError], files: [SecureIdEncryptedValueFileMetadata], translations: [SecureIdEncryptedValueFileMetadata], selfie: SecureIdEncryptedValueFileMetadata?, frontSide: SecureIdEncryptedValueFileMetadata?, backSide: SecureIdEncryptedValueFileMetadata?, encryptedMetadata: SecureIdEncryptedValueMetadata?, opaqueHash: Data) {
self.value = value
self.errors = errors
self.files = files
self.translations = translations
self.selfie = selfie
self.frontSide = frontSide
self.backSide = backSide
self.encryptedMetadata = encryptedMetadata
self.opaqueHash = opaqueHash
}
public func withRemovedErrors(_ keys: [SecureIdValueContentErrorKey]) -> SecureIdValueWithContext {
var errors = self.errors
for key in keys {
errors.removeValue(forKey: key)
}
return SecureIdValueWithContext(value: self.value, errors: errors, files: self.files, translations: self.translations, selfie: self.selfie, frontSide: self.frontSide, backSide: self.backSide, encryptedMetadata: self.encryptedMetadata, opaqueHash: self.opaqueHash)
}
}
@@ -0,0 +1,34 @@
import Foundation
public struct SecureIdValueAccessContext: Equatable {
let secret: Data
let id: Int64
public static func ==(lhs: SecureIdValueAccessContext, rhs: SecureIdValueAccessContext) -> Bool {
if lhs.secret != rhs.secret {
return false
}
if lhs.id != rhs.id {
return false
}
return true
}
}
public func generateSecureIdValueEmptyAccessContext() -> SecureIdValueAccessContext? {
return SecureIdValueAccessContext(secret: Data(), id: 0)
}
public func generateSecureIdValueAccessContext() -> SecureIdValueAccessContext? {
guard let secret = generateSecureSecretData() else {
return nil
}
let secretHashData = sha512Digest(secret)
var secretHash: Int64 = 0
secretHashData.withUnsafeBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: Int8.self)
memcpy(&secretHash, bytes.advanced(by: secretHashData.count - 8), 8)
}
return SecureIdValueAccessContext(secret: secret, id: secretHash)
}
@@ -0,0 +1,152 @@
import Foundation
import TelegramApi
public enum SecureIdValueContentErrorKey: Hashable {
case value(SecureIdValueKey)
case field(SecureIdValueContentErrorField)
case file(hash: Data)
case files(hashes: Set<Data>)
case translationFile(hash: Data)
case translationFiles(hashes: Set<Data>)
case selfie(hash: Data)
case frontSide(hash: Data)
case backSide(hash: Data)
}
public enum SecureIdValueContentErrorField: Hashable {
case personalDetails(SecureIdValueContentErrorPersonalDetailsField)
case passport(SecureIdValueContentErrorPassportField)
case internalPassport(SecureIdValueContentErrorInternalPassportField)
case driversLicense(SecureIdValueContentErrorDriversLicenseField)
case idCard(SecureIdValueContentErrorIdCardField)
case address(SecureIdValueContentErrorAddressField)
}
public enum SecureIdValueContentErrorPersonalDetailsField: String, Hashable {
case firstName = "first_name"
case lastName = "last_name"
case middleName = "middle_name"
case firstNameNative = "first_name_native"
case lastNameNative = "last_name_native"
case middleNameNative = "middle_name_native"
case birthdate = "birth_date"
case gender = "gender"
case countryCode = "country_code"
case residenceCountryCode = "residence_country_code"
}
public enum SecureIdValueContentErrorPassportField: String, Hashable {
case documentId = "document_no"
case expiryDate = "expiry_date"
}
public enum SecureIdValueContentErrorInternalPassportField: String, Hashable {
case documentId = "document_no"
case expiryDate = "expiry_date"
}
public enum SecureIdValueContentErrorDriversLicenseField: String, Hashable {
case documentId = "document_no"
case expiryDate = "expiry_date"
}
public enum SecureIdValueContentErrorIdCardField: String, Hashable {
case documentId = "document_no"
case expiryDate = "expiry_date"
}
public enum SecureIdValueContentErrorAddressField: String, Hashable {
case streetLine1 = "street_line1"
case streetLine2 = "street_line2"
case city = "city"
case state = "state"
case countryCode = "country_code"
case postCode = "post_code"
}
public typealias SecureIdValueContentError = String
func parseSecureIdValueContentErrors(dataHash: Data?, fileHashes: Set<Data>, selfieHash: Data?, frontSideHash: Data?, backSideHash: Data?, errors: [Api.SecureValueError]) -> [SecureIdValueContentErrorKey: SecureIdValueContentError] {
var result: [SecureIdValueContentErrorKey: SecureIdValueContentError] = [:]
for error in errors {
switch error {
case let .secureValueError(type, _, text):
result[.value(SecureIdValueKey(apiType: type))] = text
case let .secureValueErrorData(type, errorDataHash, field, text):
if errorDataHash.makeData() == dataHash {
switch type {
case .secureValueTypePersonalDetails:
if let parsedField = SecureIdValueContentErrorPersonalDetailsField(rawValue: field) {
result[.field(.personalDetails(parsedField))] = text
}
case .secureValueTypePassport:
if let parsedField = SecureIdValueContentErrorPassportField(rawValue: field) {
result[.field(.passport(parsedField))] = text
}
case .secureValueTypeInternalPassport:
if let parsedField = SecureIdValueContentErrorInternalPassportField(rawValue: field) {
result[.field(.internalPassport(parsedField))] = text
}
case .secureValueTypeDriverLicense:
if let parsedField = SecureIdValueContentErrorDriversLicenseField(rawValue: field) {
result[.field(.driversLicense(parsedField))] = text
}
case .secureValueTypeIdentityCard:
if let parsedField = SecureIdValueContentErrorIdCardField(rawValue: field) {
result[.field(.idCard(parsedField))] = text
}
case .secureValueTypeAddress:
if let parsedField = SecureIdValueContentErrorAddressField(rawValue: field) {
result[.field(.address(parsedField))] = text
}
default:
break
}
}
case let .secureValueErrorFile(_, fileHash, text):
if fileHashes.contains(fileHash.makeData()) {
result[.file(hash: fileHash.makeData())] = text
}
case let .secureValueErrorFiles(_, fileHash, text):
var containsAll = true
loop: for hash in fileHash {
if !fileHashes.contains(hash.makeData()) {
containsAll = false
break loop
}
}
if containsAll {
result[.files(hashes: Set(fileHash.map { $0.makeData() }))] = text
}
case let .secureValueErrorTranslationFile(_, fileHash, text):
if fileHashes.contains(fileHash.makeData()) {
result[.translationFile(hash: fileHash.makeData())] = text
}
case let .secureValueErrorTranslationFiles(_, fileHash, text):
var containsAll = true
loop: for hash in fileHash {
if !fileHashes.contains(hash.makeData()) {
containsAll = false
break loop
}
}
if containsAll {
result[.translationFiles(hashes: Set(fileHash.map { $0.makeData() }))] = text
}
case let .secureValueErrorSelfie(_, fileHash, text):
if selfieHash == fileHash.makeData() {
result[.selfie(hash: fileHash.makeData())] = text
}
case let .secureValueErrorFrontSide(_, fileHash, text):
if frontSideHash == fileHash.makeData() {
result[.frontSide(hash: fileHash.makeData())] = text
}
case let .secureValueErrorReverseSide(_, fileHash, text):
if backSideHash == fileHash.makeData() {
result[.backSide(hash: fileHash.makeData())] = text
}
}
}
return result
}
@@ -0,0 +1,23 @@
import Foundation
public enum SecureIdVerificationDocumentReference: Equatable {
case remote(SecureIdFileReference)
case uploaded(UploadedSecureIdFile)
public static func ==(lhs: SecureIdVerificationDocumentReference, rhs: SecureIdVerificationDocumentReference) -> Bool {
switch lhs {
case let .remote(file):
if case .remote(file) = rhs {
return true
} else {
return false
}
case let .uploaded(file):
if case .uploaded(file) = rhs {
return true
} else {
return false
}
}
}
}
@@ -0,0 +1,15 @@
import SwiftSignalKit
public extension TelegramEngine {
final class SecureId {
private let account: Account
init(account: Account) {
self.account = account
}
public func accessSecureId(password: String) -> Signal<(context: SecureIdAccessContext, settings: TwoStepVerificationSettings), SecureIdAccessError> {
return _internal_accessSecureId(network: self.account.network, password: password)
}
}
}
@@ -0,0 +1,126 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
public struct UploadedSecureIdFile: Equatable {
let id: Int64
let parts: Int32
let md5Checksum: String
public let fileHash: Data
let encryptedSecret: Data
}
public enum UploadSecureIdFileResult {
case progress(Float)
case result(UploadedSecureIdFile, Data)
}
public enum UploadSecureIdFileError {
case generic
}
private struct EncryptedSecureIdFile {
let data: Data
let hash: Data
let encryptedSecret: Data
}
private func encryptedSecureIdFile(context: SecureIdAccessContext, data: Data) -> EncryptedSecureIdFile? {
guard let fileSecret = generateSecureSecretData() else {
return nil
}
let paddedFileData = paddedSecureIdData(data)
let fileHash = sha256Digest(paddedFileData)
let fileSecretHash = sha512Digest(fileSecret + fileHash)
let fileKey = fileSecretHash.subdata(in: 0 ..< 32)
let fileIv = fileSecretHash.subdata(in: 32 ..< (32 + 16))
guard let encryptedFileData = encryptSecureData(key: fileKey, iv: fileIv, data: paddedFileData, decrypt: false) else {
return nil
}
let secretHash = sha512Digest(context.secret + fileHash)
let secretKey = secretHash.subdata(in: 0 ..< 32)
let secretIv = secretHash.subdata(in: 32 ..< (32 + 16))
guard let encryptedSecretData = encryptSecureData(key: secretKey, iv: secretIv, data: fileSecret, decrypt: false) else {
return nil
}
return EncryptedSecureIdFile(data: encryptedFileData, hash: fileHash, encryptedSecret: encryptedSecretData)
}
func decryptedSecureIdFileSecret(context: SecureIdAccessContext, fileHash: Data, encryptedSecret: Data) -> Data? {
let secretHash = sha512Digest(context.secret + fileHash)
let secretKey = secretHash.subdata(in: 0 ..< 32)
let secretIv = secretHash.subdata(in: 32 ..< (32 + 16))
guard let fileSecret = encryptSecureData(key: secretKey, iv: secretIv, data: encryptedSecret, decrypt: true) else {
return nil
}
guard verifySecureSecret(fileSecret) else {
return nil
}
return fileSecret
}
func decryptedSecureIdFile(context: SecureIdAccessContext, encryptedData: Data, fileHash: Data, encryptedSecret: Data) -> Data? {
guard let fileSecret = decryptedSecureIdFileSecret(context: context, fileHash: fileHash, encryptedSecret: encryptedSecret) else {
return nil
}
let fileSecretHash = sha512Digest(fileSecret + fileHash)
let fileKey = fileSecretHash.subdata(in: 0 ..< 32)
let fileIv = fileSecretHash.subdata(in: 32 ..< (32 + 16))
guard let paddedFileData = encryptSecureData(key: fileKey, iv: fileIv, data: encryptedData, decrypt: true) else {
return nil
}
let checkFileHash = sha256Digest(paddedFileData)
if fileHash != checkFileHash {
return nil
}
guard let unpaddedFileData = unpaddedSecureIdData(paddedFileData) else {
return nil
}
return unpaddedFileData
}
public func uploadSecureIdFile(context: SecureIdAccessContext, postbox: Postbox, network: Network, resource: MediaResource) -> Signal<UploadSecureIdFileResult, UploadSecureIdFileError> {
return postbox.mediaBox.resourceData(resource)
|> mapError { _ -> UploadSecureIdFileError in
}
|> mapToSignal { next -> Signal<UploadSecureIdFileResult, UploadSecureIdFileError> in
if !next.complete {
return .complete()
}
guard let data = try? Data(contentsOf: URL(fileURLWithPath: next.path)) else {
return .fail(.generic)
}
guard let encryptedData = encryptedSecureIdFile(context: context, data: data) else {
return .fail(.generic)
}
return multipartUpload(network: network, postbox: postbox, source: .data(encryptedData.data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|> mapError { _ -> UploadSecureIdFileError in
return .generic
}
|> mapToSignal { result -> Signal<UploadSecureIdFileResult, UploadSecureIdFileError> in
switch result {
case let .progress(value):
return .single(.progress(value))
case let .inputFile(file):
if case let .inputFile(id, parts, _, md5Checksum) = file {
return .single(.result(UploadedSecureIdFile(id: id, parts: parts, md5Checksum: md5Checksum, fileHash: encryptedData.hash, encryptedSecret: encryptedData.encryptedSecret), encryptedData.data))
} else {
return .fail(.generic)
}
default:
return .fail(.generic)
}
}
}
}
@@ -0,0 +1,113 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public enum SecureIdPreparePhoneVerificationError {
case generic
case flood
}
public struct SecureIdPreparePhoneVerificationPayload {
public let type: SentAuthorizationCodeType
public let nextType: AuthorizationCodeNextType?
public let timeout: Int32?
let phone: String
let phoneCodeHash: String
}
public func secureIdPreparePhoneVerification(network: Network, value: SecureIdPhoneValue) -> Signal<SecureIdPreparePhoneVerificationPayload, SecureIdPreparePhoneVerificationError> {
return network.request(Api.functions.account.sendVerifyPhoneCode(phoneNumber: value.phone, settings: .codeSettings(flags: 0, logoutTokens: nil, token: nil, appSandbox: nil)), automaticFloodWait: false)
|> mapError { error -> SecureIdPreparePhoneVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood
}
return .generic
}
|> mapToSignal { sentCode -> Signal<SecureIdPreparePhoneVerificationPayload, SecureIdPreparePhoneVerificationError> in
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
return .single(SecureIdPreparePhoneVerificationPayload(type: SentAuthorizationCodeType(apiType: type), nextType: nextType.flatMap(AuthorizationCodeNextType.init), timeout: timeout, phone: value.phone, phoneCodeHash: phoneCodeHash))
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}
}
public enum SecureIdCommitPhoneVerificationError {
case generic
case flood
case invalid
}
public func secureIdCommitPhoneVerification(postbox: Postbox, network: Network, context: SecureIdAccessContext, payload: SecureIdPreparePhoneVerificationPayload, code: String) -> Signal<SecureIdValueWithContext, SecureIdCommitPhoneVerificationError> {
return network.request(Api.functions.account.verifyPhone(phoneNumber: payload.phone, phoneCodeHash: payload.phoneCodeHash, phoneCode: code))
|> mapError { error -> SecureIdCommitPhoneVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood
} else if error.errorDescription == "PHONE_CODE_INVALID" {
return .invalid
}
return .generic
}
|> mapToSignal { _ -> Signal<SecureIdValueWithContext, SecureIdCommitPhoneVerificationError> in
return saveSecureIdValue(postbox: postbox, network: network, context: context, value: .phone(SecureIdPhoneValue(phone: payload.phone)), uploadedFiles: [:])
|> mapError { _ -> SecureIdCommitPhoneVerificationError in
return .generic
}
}
}
public enum SecureIdPrepareEmailVerificationError {
case generic
case invalidEmail
case flood
}
public struct SecureIdPrepareEmailVerificationPayload {
let email: String
public let length: Int32
}
public func secureIdPrepareEmailVerification(network: Network, value: SecureIdEmailValue) -> Signal<SecureIdPrepareEmailVerificationPayload, SecureIdPrepareEmailVerificationError> {
return network.request(Api.functions.account.sendVerifyEmailCode(purpose: .emailVerifyPurposePassport, email: value.email), automaticFloodWait: false)
|> mapError { error -> SecureIdPrepareEmailVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood
} else if error.errorDescription.hasPrefix("EMAIL_INVALID") {
return .invalidEmail
}
return .generic
}
|> map { sentCode -> SecureIdPrepareEmailVerificationPayload in
switch sentCode {
case .sentEmailCode(_, let length):
return SecureIdPrepareEmailVerificationPayload(email: value.email, length: length)
}
}
}
public enum SecureIdCommitEmailVerificationError {
case generic
case flood
case invalid
}
public func secureIdCommitEmailVerification(postbox: Postbox, network: Network, context: SecureIdAccessContext, payload: SecureIdPrepareEmailVerificationPayload, code: String) -> Signal<SecureIdValueWithContext, SecureIdCommitEmailVerificationError> {
return network.request(Api.functions.account.verifyEmail(purpose: .emailVerifyPurposePassport, verification: .emailVerificationCode(code: code)), automaticFloodWait: false)
|> mapError { error -> SecureIdCommitEmailVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood
}
return .generic
}
|> mapToSignal { _ -> Signal<SecureIdValueWithContext, SecureIdCommitEmailVerificationError> in
return saveSecureIdValue(postbox: postbox, network: network, context: context, value: .email(SecureIdEmailValue(email: payload.email)), uploadedFiles: [:])
|> mapError { _ -> SecureIdCommitEmailVerificationError in
return .generic
}
}
}