diff --git a/Package.resolved b/Package.resolved index d3548dd..16438cc 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/tikhop/ASN1Swift", "state": { "branch": null, - "revision": "b53bee03a942623db25afc5bfb80227b2cb3b425", - "version": "1.2.4" + "revision": "177417b6bf89431a0750ee640012b6aed8961c6a", + "version": "1.2.5" } } ] diff --git a/Package.swift b/Package.swift index 65c8f77..c0086df 100644 --- a/Package.swift +++ b/Package.swift @@ -1,13 +1,15 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.9 import PackageDescription let package = Package( name: "TPInAppReceipt", - platforms: [.macOS(.v10_12), - .iOS(.v10), - .tvOS(.v10), - .watchOS("6.2")], + platforms: [.macOS(.v10_13), + .iOS(.v12), + .tvOS(.v12), + .watchOS("6.2"), + .visionOS(.v1), + .macCatalyst(.v13)], products: [ .library(name: "TPInAppReceipt", targets: ["TPInAppReceipt"]), diff --git a/README.md b/README.md index 824b2ce..b1e20d6 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Installation To integrate TPInAppReceipt into your project using CocoaPods, specify it in your `Podfile`: ```ruby -platform :ios, '9.0' +platform :ios, '12.0' target 'YOUR_TARGET' do use_frameworks! @@ -66,8 +66,8 @@ swift package update ### Requirements -- iOS 10.0+ / OSX 10.11+ -- Swift 5.3+ +- iOS 12.0+ / OSX 10.13+ +- Swift 5.9+ Usage ------------- diff --git a/Sources/InAppReceipt+ASN1Decodable.swift b/Sources/InAppReceipt+ASN1Decodable.swift index 3e5a4ad..d1ea8f2 100644 --- a/Sources/InAppReceipt+ASN1Decodable.swift +++ b/Sources/InAppReceipt+ASN1Decodable.swift @@ -105,6 +105,7 @@ extension InAppReceiptPayload: ASN1Decodable var bundleIdentifierData = Data() var appVersion = "" var originalAppVersion = "" + var originalPurchaseDate: Date? var purchases = [InAppPurchase]() var opaqueValue = Data() var receiptHash = Data() @@ -140,6 +141,9 @@ extension InAppReceiptPayload: ASN1Decodable purchases.append(try valueContainer.decode(InAppPurchase.self)) case InAppReceiptField.originalAppVersion: originalAppVersion = try valueContainer.decode(String.self) + case InAppReceiptField.originalAppPurchaseDate: + let originalPurchaseDateString = try valueContainer.decode(String.self, template: .universal(ASN1Identifier.Tag.ia5String)) + originalPurchaseDate = originalPurchaseDateString.rfc3339date() case InAppReceiptField.expirationDate: let expirationDateString = try valueContainer.decode(String.self, template: .universal(ASN1Identifier.Tag.ia5String)) expirationDate = expirationDateString.rfc3339date() @@ -161,6 +165,7 @@ extension InAppReceiptPayload: ASN1Decodable self.init(bundleIdentifier: bundleIdentifier, appVersion: appVersion, originalAppVersion: originalAppVersion, + originalPurchaseDate: originalPurchaseDate, purchases: purchases, expirationDate: expirationDate, bundleIdentifierData: bundleIdentifierData, diff --git a/Sources/InAppReceipt.swift b/Sources/InAppReceipt.swift index fad0007..df0c633 100644 --- a/Sources/InAppReceipt.swift +++ b/Sources/InAppReceipt.swift @@ -19,7 +19,7 @@ public struct InAppReceiptField static let ageRating: Int32 = 10 // SHA-1 Hash static let receiptCreationDate: Int32 = 12 static let inAppPurchaseReceipt: Int32 = 17 // The receipt for an in-app purchase. - //TODO: case originalPurchaseDate = 18 + static let originalAppPurchaseDate: Int32 = 18 static let originalAppVersion: Int32 = 19 static let expirationDate: Int32 = 21 @@ -101,7 +101,13 @@ public extension InAppReceipt { return payload.originalAppVersion } - + + /// The date of the app that was originally purchased. + var originalPurchaseDate: Date? + { + return payload.originalPurchaseDate + } + /// In-app purchase's receipts var purchases: [InAppPurchase] { diff --git a/Sources/InAppReceiptPayload.swift b/Sources/InAppReceiptPayload.swift index 15b3bcc..8e8a557 100644 --- a/Sources/InAppReceiptPayload.swift +++ b/Sources/InAppReceiptPayload.swift @@ -22,7 +22,10 @@ struct InAppReceiptPayload /// The version of the app that was originally purchased. let originalAppVersion: String - + + /// The date when the app orginaly purchased. + let originalPurchaseDate: Date? + /// The date that the app receipt expires let expirationDate: Date? @@ -49,11 +52,12 @@ struct InAppReceiptPayload /// Initialize a `InAppReceipt` passing all values /// - init(bundleIdentifier: String, appVersion: String, originalAppVersion: String, purchases: [InAppPurchase], expirationDate: Date?, bundleIdentifierData: Data, opaqueValue: Data, receiptHash: Data, creationDate: Date, ageRating: String, environment: String, rawData: Data) + init(bundleIdentifier: String, appVersion: String, originalAppVersion: String, originalPurchaseDate: Date?, purchases: [InAppPurchase], expirationDate: Date?, bundleIdentifierData: Data, opaqueValue: Data, receiptHash: Data, creationDate: Date, ageRating: String, environment: String, rawData: Data) { self.bundleIdentifier = bundleIdentifier self.appVersion = appVersion self.originalAppVersion = originalAppVersion + self.originalPurchaseDate = originalPurchaseDate self.purchases = purchases self.expirationDate = expirationDate self.bundleIdentifierData = bundleIdentifierData diff --git a/Sources/Objc/InAppReceipt+Objc.swift b/Sources/Objc/InAppReceipt+Objc.swift index f33a159..a3c74c8 100644 --- a/Sources/Objc/InAppReceipt+Objc.swift +++ b/Sources/Objc/InAppReceipt+Objc.swift @@ -83,7 +83,13 @@ import TPInAppReceipt { return wrappedReceipt.originalAppVersion } - + + /// The date of the app that was originally purchased. + var originalPurchaseDate: Date? + { + return wrappedReceipt.originalPurchaseDate + } + /// In-app purchase's receipts var purchases: [InAppPurchase_Objc] { diff --git a/Sources/Validation.swift b/Sources/Validation.swift index 434faca..fc98ddb 100644 --- a/Sources/Validation.swift +++ b/Sources/Validation.swift @@ -6,14 +6,17 @@ // Copyright © 2017-2021 Pavel Tikhonenko. All rights reserved. // -#if os(iOS) || os(tvOS) -import UIKit -#elseif os(watchOS) +#if canImport(UIKit) import UIKit +#endif + +#if canImport(WatchKit) import WatchKit -#elseif os(macOS) -import IOKit +#endif + +#if canImport(Cocoa) import Cocoa +import IOKit #endif import CommonCrypto @@ -97,8 +100,7 @@ public extension InAppReceipt /// - throws: An error in the InAppReceipt domain, if verification fails func verifyBundleVersion() throws { - guard let v = Bundle.main.appVersion, - v == appVersion else + guard appVersion == Bundle.main.appVersion else { throw IARError.validationFailed(reason: .bundleVersionVerification) } @@ -110,17 +112,8 @@ public extension InAppReceipt func verifySignature() throws { try checkAppleRootCertExistence() - - // only check certificate chain of trust and signature validity after these version - if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 5.0, *) - { - #if DEBUG - try checkSignatureValidity() - #else - try checkChainOfTrust() - try checkSignatureValidity() - #endif - } + try checkSignatureValidity() + try checkChainOfTrust() } /// Verifies existence of Apple Root Certificate in bundle @@ -128,14 +121,13 @@ public extension InAppReceipt /// - throws: An error in the InAppReceipt domain, if Apple Root Certificate does not exist fileprivate func checkAppleRootCertExistence() throws { - guard let certPath = rootCertificatePath, - FileManager.default.fileExists(atPath: certPath) else + guard let rootCertificatePath, + FileManager.default.fileExists(atPath: rootCertificatePath) else { throw IARError.validationFailed(reason: .signatureValidation(.appleIncRootCertificateNotFound)) } } - @available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 5.0, *) func checkChainOfTrust() throws { // Validate chain of trust of certificate @@ -181,17 +173,17 @@ public extension InAppReceipt policy, &wwdcTrust) - guard worldwideDevCertVerifyStatus == errSecSuccess && wwdcTrust != nil else + guard worldwideDevCertVerifyStatus == errSecSuccess, let wwdcTrust else { throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust)) } // verify iTunes cert in the receipt is signed by worldwide developer cert, which is signed by Apple Root Cert - let iTunesCertVerifystatus = SecTrustCreateWithCertificates([iTunesCertSec, worldwideDevCertSec, rootCertSec] as AnyObject, + let iTunesCertVerifyStatus = SecTrustCreateWithCertificates([iTunesCertSec, worldwideDevCertSec, rootCertSec] as AnyObject, policy, &iTunesTrust) - guard iTunesCertVerifystatus == errSecSuccess && iTunesTrust != nil else + guard iTunesCertVerifyStatus == errSecSuccess, let iTunesTrust else { throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust)) } @@ -201,12 +193,12 @@ public extension InAppReceipt if #available(OSX 10.14, iOS 12.0, tvOS 12.0, *) { var error: CFError? - guard SecTrustEvaluateWithError(wwdcTrust!, &error) else + guard SecTrustEvaluateWithError(wwdcTrust, &error) else { throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust)) } } else { - guard SecTrustEvaluate(wwdcTrust!, &secTrustResult) == errSecSuccess else + guard SecTrustEvaluate(wwdcTrust, &secTrustResult) == errSecSuccess else { throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust)) } @@ -215,12 +207,12 @@ public extension InAppReceipt if #available(OSX 10.14, iOS 12.0, tvOS 12.0, *) { var error: CFError? - guard SecTrustEvaluateWithError(iTunesTrust!, &error) else + guard SecTrustEvaluateWithError(iTunesTrust, &error) else { throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust)) } } else { - guard SecTrustEvaluate(iTunesTrust!, &secTrustResult) == errSecSuccess else + guard SecTrustEvaluate(iTunesTrust, &secTrustResult) == errSecSuccess else { throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust)) } @@ -284,22 +276,24 @@ public extension InAppReceipt fileprivate func guid() -> Data { -#if os(watchOS) +#if targetEnvironment(macCatalyst) || os(macOS) + if let guid = getMacAddress() + { + return guid + }else{ + assertionFailure("Failed to retrieve guid") + } + + return Data() // Never get called +#else + +#if canImport(WatchKit) var uuidBytes = WKInterfaceDevice.current().identifierForVendor!.uuid - return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes)) -#elseif !targetEnvironment(macCatalyst) && (os(iOS) || os(tvOS)) +#elseif canImport(UIKit) var uuidBytes = UIDevice.current.identifierForVendor!.uuid - return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes)) -#elseif targetEnvironment(macCatalyst) || os(macOS) +#endif - if let guid = getMacAddress() - { - return guid - }else{ - assertionFailure("Failed to retrieve guid") - } - - return Data() // Never get called + return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes)) #endif } diff --git a/TPInAppReceipt.podspec b/TPInAppReceipt.podspec index 9f4e84a..bfe7734 100644 --- a/TPInAppReceipt.podspec +++ b/TPInAppReceipt.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "TPInAppReceipt" - s.version = "3.3.4" + s.version = "3.4.0" s.summary = "Reading and Validating In App Purchase Receipt Locally" s.description = "A lightweight iOS/OSX library for reading and validating Apple In App Purchase Receipt locally. Pure swift, No OpenSSL!" @@ -9,21 +9,22 @@ Pod::Spec.new do |s| s.license = "MIT" s.source = { :git => "https://github.com/tikhop/TPInAppReceipt.git", :tag => "#{s.version}" } - s.author = { "Pavel Tikhonenko" => "hi@tikhop.com" } + s.author = { "tikhop" => "hi@tikhop.com" } s.swift_versions = ['5.3'] - s.ios.deployment_target = '10.0' - s.osx.deployment_target = '10.12' - s.tvos.deployment_target = '10.0' + s.ios.deployment_target = '12.0' + s.osx.deployment_target = '10.13' + s.tvos.deployment_target = '12.0' s.watchos.deployment_target = '6.2' - s.requires_arc = true - + s.visionos.deployment_target = '1.0' + + s.requires_arc = true s.subspec 'Core' do |core| core.exclude_files = "Sources/Objc/*.{swift}" core.source_files = "Sources/*.{swift}" core.resources = "Sources/AppleIncRootCertificate.cer", "Sources/StoreKitTestCertificate.cer" - core.dependency 'ASN1Swift', '~> 1.2.3' + core.dependency 'ASN1Swift', '~> 1.2.5' end s.subspec 'Objc' do |objc| diff --git a/Tests/TPInAppReceiptTests/PerformanceTests.swift b/Tests/TPInAppReceiptTests/PerformanceTests.swift index 56686c8..7ba83a9 100644 --- a/Tests/TPInAppReceiptTests/PerformanceTests.swift +++ b/Tests/TPInAppReceiptTests/PerformanceTests.swift @@ -39,7 +39,7 @@ class PerformanceTests: XCTestCase self.measure { do { - try receipt.verify() + try receipt.validate() }catch{ XCTFail("Unable to verify: \(error)") } diff --git a/Tests/TPInAppReceiptTests/TPInAppReceiptTests.swift b/Tests/TPInAppReceiptTests/TPInAppReceiptTests.swift index b7193b0..884a22c 100644 --- a/Tests/TPInAppReceiptTests/TPInAppReceiptTests.swift +++ b/Tests/TPInAppReceiptTests/TPInAppReceiptTests.swift @@ -2,20 +2,11 @@ import XCTest @testable import TPInAppReceipt final class TPInAppReceiptTests: XCTestCase { - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - - } - - func testCrashReceipts() - { + func testCrashReceipts() { var r = try? InAppReceipt(receiptData: noOriginalPurchaseDateCrashReceipt) } - func testNewReceipt() - { + func testNewReceipt() { self.measure { let r = try! InAppReceipt(receiptData: newReceipt) print(r.creationDate) @@ -23,8 +14,7 @@ final class TPInAppReceiptTests: XCTestCase { } - func testLegacyReceipt() - { + func testLegacyReceipt() { self.measure { let r = try! InAppReceipt(receiptData: legacyReceipt) }