mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
260 lines
8.1 KiB
Swift
260 lines
8.1 KiB
Swift
import Foundation
|
|
|
|
private struct Asn1Tag {
|
|
static let integer: Int32 = 0x02
|
|
static let octetString: Int32 = 0x04
|
|
static let objectIdentifier: Int32 = 0x06
|
|
static let sequence: Int32 = 0x10
|
|
static let set: Int32 = 0x11
|
|
static let utf8String: Int32 = 0x0c
|
|
static let date: Int32 = 0x16
|
|
}
|
|
|
|
private struct Asn1Entry {
|
|
let tag: Int32
|
|
let data: Data
|
|
let length: Int
|
|
}
|
|
|
|
private func parse(_ data: Data, startIndex: Int = 0) -> Asn1Entry {
|
|
var index = startIndex
|
|
var value = data[index]
|
|
index += 1
|
|
var tagValue = Int32(value & 0x1f)
|
|
if tagValue == 31 {
|
|
value = data[index]
|
|
index += 1
|
|
while (value & 0x80) != 0 {
|
|
tagValue <<= 8
|
|
tagValue |= Int32(value & 0x7f)
|
|
value = data[index]
|
|
index += 1
|
|
}
|
|
tagValue <<= 8
|
|
tagValue |= Int32(value & 0x7f)
|
|
}
|
|
|
|
var length = 0
|
|
var nextTag = 0
|
|
value = data[index]
|
|
index += 1
|
|
if value & 0x80 == 0 {
|
|
length = Int(value)
|
|
nextTag = index + length
|
|
} else if value != 0x80 {
|
|
let octetsCount = Int(value & 0x7f)
|
|
for _ in 0 ..< octetsCount {
|
|
length <<= 8
|
|
value = data[index]
|
|
index += 1
|
|
length |= Int(value) & 0xff
|
|
}
|
|
nextTag = index + length
|
|
} else {
|
|
var scanIndex = index
|
|
while data[scanIndex] != 0 && data[scanIndex + 1] != 0 {
|
|
scanIndex += 1
|
|
}
|
|
length = scanIndex - index
|
|
nextTag = scanIndex + 2
|
|
}
|
|
return Asn1Entry(tag: tagValue, data: data.subdata(in: index ..< (index + length)), length: nextTag - startIndex)
|
|
}
|
|
|
|
private func parseSequence(_ data: Data) -> [Asn1Entry] {
|
|
var result : [Asn1Entry] = []
|
|
var index = 0
|
|
while index < data.count {
|
|
let entry = parse(data, startIndex: index)
|
|
result.append(entry)
|
|
index += entry.length
|
|
}
|
|
return result
|
|
}
|
|
|
|
private func parseInteger(_ data: Data) -> Int32 {
|
|
let length = data.count
|
|
var value: Int32 = 0
|
|
for i in 0 ..< length {
|
|
if i == 0 {
|
|
value = Int32(data[i] & 0x7f)
|
|
} else {
|
|
value <<= 8
|
|
value |= Int32(data[i])
|
|
}
|
|
}
|
|
if length > 0 && data[0] & 0x80 != 0 {
|
|
let complement: Int32 = 1 << (length * 8)
|
|
value -= complement
|
|
}
|
|
return value
|
|
}
|
|
|
|
private func parseObjectIdentifier(_ data: Data, startIndex: Int = 0, length: Int? = nil) -> [Int32] {
|
|
let dataLen = length ?? data.count
|
|
var index = startIndex
|
|
var identifier: [Int32] = []
|
|
while index < startIndex + dataLen {
|
|
var subidentifier: Int32 = 0
|
|
var value = data[index]
|
|
index += 1
|
|
while (value & 0x80) != 0 {
|
|
subidentifier <<= 7
|
|
subidentifier |= Int32(value & 0x7f)
|
|
value = data[index]
|
|
index += 1
|
|
}
|
|
subidentifier <<= 7
|
|
subidentifier |= Int32(value & 0x7f)
|
|
identifier.append(subidentifier)
|
|
}
|
|
return identifier
|
|
}
|
|
|
|
private struct ObjectIdentifier {
|
|
static let pkcs7Data: [Int32] = [42, 840, 113549, 1, 7, 1]
|
|
static let pkcs7SignedData: [Int32] = [42, 840, 113549, 1, 7, 2]
|
|
}
|
|
|
|
struct Receipt {
|
|
fileprivate struct Tag {
|
|
static let purchases: Int32 = 17
|
|
}
|
|
|
|
struct Purchase {
|
|
fileprivate struct Tag {
|
|
static let productIdentifier: Int32 = 1702
|
|
static let transactionIdentifier: Int32 = 1703
|
|
static let expirationDate: Int32 = 1708
|
|
}
|
|
|
|
let productId: String
|
|
let transactionId: String
|
|
let expirationDate: Date
|
|
}
|
|
|
|
let purchases: [Purchase]
|
|
}
|
|
|
|
func parseReceipt(_ data: Data) -> Receipt? {
|
|
let root = parseSequence(data)
|
|
guard root.count == 1 && root[0].tag == Asn1Tag.sequence else {
|
|
return nil
|
|
}
|
|
|
|
let rootSeq = parseSequence(root[0].data)
|
|
guard rootSeq.count == 2 && rootSeq[0].tag == Asn1Tag.objectIdentifier && parseObjectIdentifier(rootSeq[0].data) == ObjectIdentifier.pkcs7SignedData else {
|
|
return nil
|
|
}
|
|
|
|
let signedData = parseSequence(rootSeq[1].data)
|
|
guard signedData.count == 1 && signedData[0].tag == Asn1Tag.sequence else {
|
|
return nil
|
|
}
|
|
|
|
let signedDataSeq = parseSequence(signedData[0].data)
|
|
guard signedDataSeq.count > 3 && signedDataSeq[2].tag == Asn1Tag.sequence else {
|
|
return nil
|
|
}
|
|
|
|
let contentData = parseSequence(signedDataSeq[2].data)
|
|
guard contentData.count == 2 && contentData[0].tag == Asn1Tag.objectIdentifier && parseObjectIdentifier(contentData[0].data) == ObjectIdentifier.pkcs7Data else {
|
|
return nil
|
|
}
|
|
|
|
let payload = parse(contentData[1].data)
|
|
guard payload.tag == Asn1Tag.octetString else {
|
|
return nil
|
|
}
|
|
|
|
let payloadRoot = parse(payload.data)
|
|
guard payloadRoot.tag == Asn1Tag.set else {
|
|
return nil
|
|
}
|
|
|
|
var purchases: [Receipt.Purchase] = []
|
|
|
|
let receiptAttributes = parseSequence(payloadRoot.data)
|
|
for attribute in receiptAttributes {
|
|
if attribute.tag != Asn1Tag.sequence { continue }
|
|
let attributeEntries = parseSequence(attribute.data)
|
|
guard attributeEntries.count == 3 && attributeEntries[0].tag == Asn1Tag.integer && attributeEntries[1].tag == Asn1Tag.integer && attributeEntries[2].tag == Asn1Tag.octetString else { return nil
|
|
}
|
|
|
|
let type = parseInteger(attributeEntries[0].data)
|
|
let value = attributeEntries[2].data
|
|
switch (type) {
|
|
case Receipt.Tag.purchases:
|
|
if let purchase = parsePurchaseAttributes(value) {
|
|
purchases.append(purchase)
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
return Receipt(purchases: purchases)
|
|
}
|
|
|
|
private func parseRfc3339Date(_ str: String) -> Date? {
|
|
let posixLocale = Locale(identifier: "en_US_POSIX")
|
|
|
|
let formatter1 = DateFormatter()
|
|
formatter1.locale = posixLocale
|
|
formatter1.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssX5"
|
|
formatter1.timeZone = TimeZone(secondsFromGMT: 0)
|
|
|
|
let result = formatter1.date(from: str)
|
|
if result != nil {
|
|
return result
|
|
}
|
|
|
|
let formatter2 = DateFormatter()
|
|
formatter2.locale = posixLocale
|
|
formatter2.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSSSSX5"
|
|
formatter2.timeZone = TimeZone(secondsFromGMT: 0)
|
|
|
|
return formatter2.date(from: str)
|
|
}
|
|
|
|
private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
|
|
let root = parse(data)
|
|
guard root.tag == Asn1Tag.set else {
|
|
return nil
|
|
}
|
|
|
|
var productId: String?
|
|
var transactionId: String?
|
|
var expirationDate: Date?
|
|
|
|
let receiptAttributes = parseSequence(root.data)
|
|
for attribute in receiptAttributes {
|
|
if attribute.tag != Asn1Tag.sequence { continue }
|
|
let attributeEntries = parseSequence(attribute.data)
|
|
guard attributeEntries.count == 3 && attributeEntries[0].tag == Asn1Tag.integer && attributeEntries[1].tag == Asn1Tag.integer && attributeEntries[2].tag == Asn1Tag.octetString else { return nil
|
|
}
|
|
|
|
let type = parseInteger(attributeEntries[0].data)
|
|
let value = attributeEntries[2].data
|
|
switch (type) {
|
|
case Receipt.Purchase.Tag.productIdentifier:
|
|
let valEntry = parse(value)
|
|
guard valEntry.tag == Asn1Tag.utf8String else { return nil }
|
|
productId = String(bytes: valEntry.data, encoding: .utf8)
|
|
case Receipt.Purchase.Tag.transactionIdentifier:
|
|
let valEntry = parse(value)
|
|
guard valEntry.tag == Asn1Tag.utf8String else { return nil }
|
|
transactionId = String(bytes: valEntry.data, encoding: .utf8)
|
|
case Receipt.Purchase.Tag.expirationDate:
|
|
let valEntry = parse(value)
|
|
guard valEntry.tag == Asn1Tag.date else { return nil }
|
|
expirationDate = parseRfc3339Date(String(bytes: valEntry.data, encoding: .utf8) ?? "")
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
guard let productId, let transactionId, let expirationDate else {
|
|
return nil
|
|
}
|
|
return Receipt.Purchase(productId: productId, transactionId: transactionId, expirationDate: expirationDate)
|
|
}
|