Skip to content

Commit

Permalink
Merge pull request #118 from tikhop/develop
Browse files Browse the repository at this point in the history
chore: Release 3.4.0
  • Loading branch information
tikhop authored Feb 8, 2024
2 parents 0922d5f + 20cf626 commit 5c1ecd9
Show file tree
Hide file tree
Showing 11 changed files with 86 additions and 78 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
]
Expand Down
12 changes: 7 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -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"]),
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down Expand Up @@ -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
-------------
Expand Down
5 changes: 5 additions & 0 deletions Sources/InAppReceipt+ASN1Decodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -161,6 +165,7 @@ extension InAppReceiptPayload: ASN1Decodable
self.init(bundleIdentifier: bundleIdentifier,
appVersion: appVersion,
originalAppVersion: originalAppVersion,
originalPurchaseDate: originalPurchaseDate,
purchases: purchases,
expirationDate: expirationDate,
bundleIdentifierData: bundleIdentifierData,
Expand Down
10 changes: 8 additions & 2 deletions Sources/InAppReceipt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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]
{
Expand Down
8 changes: 6 additions & 2 deletions Sources/InAppReceiptPayload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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
Expand Down
8 changes: 7 additions & 1 deletion Sources/Objc/InAppReceipt+Objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]
{
Expand Down
76 changes: 35 additions & 41 deletions Sources/Validation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand All @@ -110,32 +112,22 @@ 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
///
/// - 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
Expand Down Expand Up @@ -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))
}
Expand All @@ -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))
}
Expand All @@ -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))
}
Expand Down Expand Up @@ -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
}

Expand Down
17 changes: 9 additions & 8 deletions TPInAppReceipt.podspec
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
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!"

s.homepage = "https://github.com/tikhop/TPInAppReceipt"
s.license = "MIT"
s.source = { :git => "https://github.com/tikhop/TPInAppReceipt.git", :tag => "#{s.version}" }

s.author = { "Pavel Tikhonenko" => "[email protected]" }
s.author = { "tikhop" => "[email protected]" }

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|
Expand Down
2 changes: 1 addition & 1 deletion Tests/TPInAppReceiptTests/PerformanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class PerformanceTests: XCTestCase
self.measure {
do
{
try receipt.verify()
try receipt.validate()
}catch{
XCTFail("Unable to verify: \(error)")
}
Expand Down
16 changes: 3 additions & 13 deletions Tests/TPInAppReceiptTests/TPInAppReceiptTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,19 @@ 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)
}

}

func testLegacyReceipt()
{
func testLegacyReceipt() {
self.measure {
let r = try! InAppReceipt(receiptData: legacyReceipt)
}
Expand Down

0 comments on commit 5c1ecd9

Please sign in to comment.