Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

chore: Release 3.4.0 #118

Merged
merged 8 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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" => "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|
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
Loading