Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92409e4498 | |||
| b2764203d4 | |||
| 9817657584 | |||
| a0f87dbc46 | |||
| 20d7a7a2ba |
@@ -33,9 +33,9 @@ struct HekateDemoViewModel {
|
||||
guard let result = self.lastValidationResult else { return "(No result)" }
|
||||
|
||||
switch result {
|
||||
case .success(let receipt):
|
||||
case .success(let receipt, _, _):
|
||||
return "Valid\n" + receipt.description
|
||||
case .error(let error):
|
||||
case .error(let error, _, _):
|
||||
return "Invalid: \(error)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,13 @@ public struct LocalReceiptValidator {
|
||||
|
||||
/// Validates a local receipt and returns the result using the passed parameters.
|
||||
public func validateReceipt(parameters: Parameters = Parameters.allSteps) -> Result {
|
||||
var data: Data?
|
||||
var deviceIdData: Data?
|
||||
do {
|
||||
deviceIdData = parameters.deviceIdentifier.getData()
|
||||
guard let receiptData = parameters.receiptOrigin.loadData() else { throw Error.couldNotFindReceipt }
|
||||
|
||||
data = receiptData
|
||||
let receiptContainer = try self.extractPKCS7Container(data: receiptData)
|
||||
|
||||
if parameters.shouldValidateSignaturePresence {
|
||||
@@ -49,14 +53,14 @@ public struct LocalReceiptValidator {
|
||||
try self.validateProperties(receipt: receipt, validations: parameters.propertyValidations)
|
||||
|
||||
if parameters.shouldValidateHash {
|
||||
guard let deviceIdentifierData = parameters.deviceIdentifier.getData() else { throw Error.deviceIdentifierNotDeterminable }
|
||||
guard let deviceIdentifierData = deviceIdData else { throw Error.deviceIdentifierNotDeterminable }
|
||||
|
||||
try self.validateHash(receipt: receipt, deviceIdentifierData: deviceIdentifierData)
|
||||
}
|
||||
return .success(receipt)
|
||||
return .success(receipt, receiptData: receiptData, deviceIdentifier: deviceIdData)
|
||||
} catch {
|
||||
assert(error is LocalReceiptValidator.Error)
|
||||
return .error(error as? LocalReceiptValidator.Error ?? .unknown)
|
||||
return .error(error as? LocalReceiptValidator.Error ?? .unknown, receiptData: data, deviceIdentifier: deviceIdData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +98,7 @@ public struct LocalReceiptValidator {
|
||||
private extension LocalReceiptValidator {
|
||||
|
||||
func validateHash(receipt: Receipt, deviceIdentifierData: Data) throws {
|
||||
// Make sure that the ParsedReceipt instances has non-nil values needed for hash comparison
|
||||
// Make sure that the Receipt instances has non-nil values needed for hash comparison
|
||||
guard let receiptOpaqueValueData = receipt.opaqueValue else { throw Error.incorrectHash }
|
||||
guard let receiptBundleIdData = receipt.bundleIdData else { throw Error.incorrectHash }
|
||||
guard let receiptHashData = receipt.sha1Hash else { throw Error.incorrectHash }
|
||||
@@ -193,65 +197,65 @@ private extension LocalReceiptValidator {
|
||||
guard let contents = pkcs7.pkcs7.pointee.d.sign.pointee.contents, let octets = contents.pointee.d.data else { throw Error.malformedReceipt }
|
||||
guard let initialPointer = UnsafePointer(octets.pointee.data) else { throw Error.malformedReceipt }
|
||||
let length = Int(octets.pointee.length)
|
||||
var parsedReceipt = Receipt()
|
||||
var receipt = Receipt()
|
||||
|
||||
try self.parseASN1Set(pointer: initialPointer, length: length) { attributeType, value in
|
||||
guard let attribute = KnownReceiptAttribute(rawValue: attributeType) else { return }
|
||||
|
||||
switch attribute {
|
||||
case .bundleIdentifier:
|
||||
parsedReceipt.bundleIdData = value.dataValue
|
||||
parsedReceipt.bundleIdentifier = value.unwrappedStringValue
|
||||
receipt.bundleIdData = value.dataValue
|
||||
receipt.bundleIdentifier = value.unwrappedStringValue
|
||||
case .appVersion:
|
||||
parsedReceipt.appVersion = value.unwrappedStringValue
|
||||
receipt.appVersion = value.unwrappedStringValue
|
||||
case .opaqueValue:
|
||||
parsedReceipt.opaqueValue = value.dataValue
|
||||
receipt.opaqueValue = value.dataValue
|
||||
case .sha1Hash:
|
||||
parsedReceipt.sha1Hash = value.dataValue
|
||||
receipt.sha1Hash = value.dataValue
|
||||
case .inAppPurchaseReceipts:
|
||||
guard let pointer = value.valuePointer else { break }
|
||||
|
||||
let iapReceipt = try parseInAppPurchaseReceipt(pointer: pointer, length: value.length)
|
||||
parsedReceipt.inAppPurchaseReceipts.append(iapReceipt)
|
||||
receipt.inAppPurchaseReceipts.append(iapReceipt)
|
||||
case .receiptCreationDate:
|
||||
parsedReceipt.receiptCreationDate = value.unwrappedDateValue
|
||||
receipt.receiptCreationDate = value.unwrappedDateValue
|
||||
case .originalAppVersion:
|
||||
parsedReceipt.originalAppVersion = value.unwrappedStringValue
|
||||
receipt.originalAppVersion = value.unwrappedStringValue
|
||||
case .expirationDate:
|
||||
parsedReceipt.expirationDate = value.unwrappedDateValue
|
||||
receipt.expirationDate = value.unwrappedDateValue
|
||||
break
|
||||
}
|
||||
}
|
||||
return parsedReceipt
|
||||
return receipt
|
||||
}
|
||||
|
||||
private func parseInAppPurchaseReceipt(pointer: UnsafePointer<UInt8>, length: Int) throws -> InAppPurchaseReceipt {
|
||||
var parsedInAppPurchaseReceipt = InAppPurchaseReceipt()
|
||||
var inAppPurchaseReceipt = InAppPurchaseReceipt()
|
||||
try self.parseASN1Set(pointer: pointer, length: length) { attributeType, value in
|
||||
guard let attribute = KnownInAppPurchaseAttribute(rawValue: attributeType) else { return }
|
||||
|
||||
switch attribute {
|
||||
case .quantity:
|
||||
parsedInAppPurchaseReceipt.quantity = value.intValue
|
||||
inAppPurchaseReceipt.quantity = value.intValue
|
||||
case .productIdentifier:
|
||||
parsedInAppPurchaseReceipt.productIdentifier = value.unwrappedStringValue
|
||||
inAppPurchaseReceipt.productIdentifier = value.unwrappedStringValue
|
||||
case .transactionIdentifier:
|
||||
parsedInAppPurchaseReceipt.transactionIdentifier = value.unwrappedStringValue
|
||||
inAppPurchaseReceipt.transactionIdentifier = value.unwrappedStringValue
|
||||
case .originalTransactionIdentifier:
|
||||
parsedInAppPurchaseReceipt.originalTransactionIdentifier = value.unwrappedStringValue
|
||||
inAppPurchaseReceipt.originalTransactionIdentifier = value.unwrappedStringValue
|
||||
case .purchaseDate:
|
||||
parsedInAppPurchaseReceipt.purchaseDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.purchaseDate = value.unwrappedDateValue
|
||||
case .originalPurchaseDate:
|
||||
parsedInAppPurchaseReceipt.originalPurchaseDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.originalPurchaseDate = value.unwrappedDateValue
|
||||
case .subscriptionExpirationDate:
|
||||
parsedInAppPurchaseReceipt.subscriptionExpirationDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.subscriptionExpirationDate = value.unwrappedDateValue
|
||||
case .cancellationDate:
|
||||
parsedInAppPurchaseReceipt.cancellationDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.cancellationDate = value.unwrappedDateValue
|
||||
case .webOrderLineItemId:
|
||||
parsedInAppPurchaseReceipt.webOrderLineItemId = value.intValue
|
||||
inAppPurchaseReceipt.webOrderLineItemId = value.intValue
|
||||
}
|
||||
}
|
||||
return parsedInAppPurchaseReceipt
|
||||
return inAppPurchaseReceipt
|
||||
}
|
||||
|
||||
private func parseASN1Set(pointer initialPointer: UnsafePointer<UInt8>, length: Int, valueAttributeAction: (_ attributeType: Int32, _ value: ASN1Object) throws -> Void) throws {
|
||||
@@ -284,7 +288,7 @@ private extension LocalReceiptValidator {
|
||||
|
||||
private extension LocalReceiptValidator {
|
||||
|
||||
/// See ParsedReceipt.swift for details and a link to Apple reference
|
||||
/// See Receipt.swift for details and a link to Apple reference
|
||||
enum KnownReceiptAttribute: Int32 {
|
||||
case bundleIdentifier = 2
|
||||
case appVersion = 3
|
||||
@@ -303,7 +307,7 @@ private extension LocalReceiptValidator {
|
||||
// - and of unknown type 14(L=3), 25(L=3), 11(L=4), 13(L=4), 1(L=6), 9(L=6), 16(L=6), 15(L=8), 7(L=66), 6(L=69 variable)
|
||||
}
|
||||
|
||||
/// See ParsedReceipt.swift for details and a link to Apple reference
|
||||
/// See Receipt.swift for details and a link to Apple reference
|
||||
enum KnownInAppPurchaseAttribute: Int32 {
|
||||
case quantity = 1701
|
||||
case productIdentifier = 1702
|
||||
@@ -323,12 +327,12 @@ extension LocalReceiptValidator {
|
||||
|
||||
public enum Result {
|
||||
|
||||
case success(Receipt)
|
||||
case error(LocalReceiptValidator.Error)
|
||||
case success(Receipt, receiptData: Data, deviceIdentifier: Data?)
|
||||
case error(LocalReceiptValidator.Error, receiptData: Data?, deviceIdentifier: Data?)
|
||||
|
||||
public var receipt: Receipt? {
|
||||
switch self {
|
||||
case .success(let receipt):
|
||||
case .success(let receipt, _, _):
|
||||
return receipt
|
||||
case .error:
|
||||
return nil
|
||||
@@ -339,10 +343,30 @@ extension LocalReceiptValidator {
|
||||
switch self {
|
||||
case .success:
|
||||
return nil
|
||||
case .error(let error):
|
||||
case .error(let error, _, _):
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
/// The receipt data if it could be loaded
|
||||
public var receiptData: Data? {
|
||||
switch self {
|
||||
case .success(_, let data, _):
|
||||
return data
|
||||
case .error(_, let data, _):
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
/// The device identifier if it could be determined
|
||||
public var deviceIdentifier: Data? {
|
||||
switch self {
|
||||
case .success(_, _, let data):
|
||||
return data
|
||||
case .error(_, _, let data):
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ extension Receipt: AutoEquatable {}
|
||||
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
extension Receipt: CustomStringConvertible {
|
||||
extension Receipt: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
let formatter = StringFormatter()
|
||||
@@ -91,11 +91,15 @@ extension Receipt: CustomStringConvertible {
|
||||
("expirationDate", formatter.format(self.expirationDate)),
|
||||
("inAppPurchaseReceipts", formatter.format(self.inAppPurchaseReceipts))
|
||||
]
|
||||
return "ParsedReceipt(\n" + formatter.format(props) + "\n)"
|
||||
return "Receipt(\n" + formatter.format(props) + "\n)"
|
||||
}
|
||||
|
||||
public var debugDescription: String {
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ParsedInAppPurchaseReceipt
|
||||
// MARK: - InAppPurchaseReceipt
|
||||
|
||||
/// An In-App-Purchase Receipt as Parsed from a receipt file.
|
||||
///
|
||||
@@ -169,7 +173,7 @@ public struct InAppPurchaseReceipt {
|
||||
/// This value is a unique ID that identifies purchase events across devices, including subscription renewal purchase events.
|
||||
public internal(set) var webOrderLineItemId: Int?
|
||||
|
||||
/// For documentation see ParsedInAppPurchaseReceipt itself.
|
||||
/// For documentation see InAppPurchaseReceipt itself.
|
||||
public init(quantity: Int?, productIdentifier: String?, transactionIdentifier: String?, originalTransactionIdentifier: String?, purchaseDate: Date?, originalPurchaseDate: Date?, subscriptionExpirationDate: Date?, cancellationDate: Date?, webOrderLineItemId: Int?) {
|
||||
self.quantity = quantity
|
||||
self.productIdentifier = productIdentifier
|
||||
@@ -191,7 +195,7 @@ extension InAppPurchaseReceipt: AutoEquatable {}
|
||||
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
extension InAppPurchaseReceipt: CustomStringConvertible {
|
||||
extension InAppPurchaseReceipt: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
let formatter = StringFormatter()
|
||||
@@ -206,7 +210,11 @@ extension InAppPurchaseReceipt: CustomStringConvertible {
|
||||
("cancellationDate", formatter.format(self.cancellationDate)),
|
||||
("webOrderLineItemId", formatter.format(self.webOrderLineItemId))
|
||||
]
|
||||
return "ParsedInAppPurchaseReceipt(\n" + formatter.format(props) + "\n)"
|
||||
return "InAppPurchaseReceipt(\n" + formatter.format(props) + "\n)"
|
||||
}
|
||||
|
||||
public var debugDescription: String {
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ private func compareArrays<T>(lhs: [T], rhs: [T], compare: (_ lhs: T, _ rhs: T)
|
||||
}
|
||||
|
||||
// MARK: - AutoEquatable for classes, protocols, structs
|
||||
// MARK: - ParsedInAppPurchaseReceipt AutoEquatable
|
||||
// MARK: - InAppPurchaseReceipt AutoEquatable
|
||||
extension InAppPurchaseReceipt: Equatable {}
|
||||
public func == (lhs: InAppPurchaseReceipt, rhs: InAppPurchaseReceipt) -> Bool {
|
||||
guard compareOptionals(lhs: lhs.quantity, rhs: rhs.quantity, compare: ==) else { return false }
|
||||
@@ -37,7 +37,7 @@ public func == (lhs: InAppPurchaseReceipt, rhs: InAppPurchaseReceipt) -> Bool {
|
||||
guard compareOptionals(lhs: lhs.webOrderLineItemId, rhs: rhs.webOrderLineItemId, compare: ==) else { return false }
|
||||
return true
|
||||
}
|
||||
// MARK: - ParsedReceipt AutoEquatable
|
||||
// MARK: - Receipt AutoEquatable
|
||||
extension Receipt: Equatable {}
|
||||
public func == (lhs: Receipt, rhs: Receipt) -> Bool {
|
||||
guard compareOptionals(lhs: lhs.bundleIdentifier, rhs: rhs.bundleIdentifier, compare: ==) else { return false }
|
||||
|
||||
Reference in New Issue
Block a user