diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
new file mode 100644
index 0000000..585df56
--- /dev/null
+++ b/.github/workflows/pull_request.yml
@@ -0,0 +1,61 @@
+name: CI
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ macos:
+ name: macOS (Build & Test)
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Build
+ run: swift build -v
+ - name: Test
+ run: swift test -v
+
+ ios:
+ name: iOS (Build & Test)
+ runs-on: macos-26
+ steps:
+ - uses: actions/checkout@v4
+ - name: List available simulators
+ run: |
+ xcrun simctl list devices
+ xcodebuild -showsdks
+ - name: Build
+ run: xcodebuild build -scheme TPInAppReceipt -destination 'platform=iOS Simulator,OS=latest,name=iPhone 17' -skipPackagePluginValidation SWIFT_STRICT_CONCURRENCY=targeted
+ - name: Test
+ run: xcodebuild test -scheme TPInAppReceipt -destination 'platform=iOS Simulator,OS=latest,name=iPhone 17' -skipPackagePluginValidation SWIFT_STRICT_CONCURRENCY=targeted
+
+ linux:
+ name: Linux (Build & Test)
+ runs-on: ubuntu-latest
+ container: swift:6.0
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Show Swift & OS info
+ run: |
+ swift --version
+ uname -a
+
+ - name: Restore swift package cache
+ uses: actions/cache@v4
+ with:
+ path: |
+ .build
+ .swiftpm
+ key: ${{ runner.os }}-swift-${{ hashFiles('**/Package.resolved') }}
+
+ - name: Resolve Packages
+ run: swift package resolve -v
+
+ - name: Build
+ run: swift build -v
+
+ - name: Test
+ run: swift test -v
diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml
deleted file mode 100644
index 59e3574..0000000
--- a/.github/workflows/swift.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Swift
-
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
-
-jobs:
- build:
-
- runs-on: macos-latest
-
- steps:
- - uses: actions/checkout@v2
- - name: Build
- run: swift build -v
-
diff --git a/.gitignore b/.gitignore
index 1328f51..0077b67 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,74 +1,13 @@
-# Xcode
-#
-# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
-
-## Build generated
-build/
-DerivedData/
-Packages/
-iOS Example/ViewController.swift
-
-## Various settings
-*.pbxuser
-!default.pbxuser
-*.mode1v3
-!default.mode1v3
-*.mode2v3
-!default.mode2v3
-*.perspectivev3
-!default.perspectivev3
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
xcuserdata/
-
-## Other
-*.moved-aside
-*.xcuserstate
-
-## Obj-C/Swift specific
-*.hmap
-*.ipa
-*.dSYM.zip
-*.dSYM
-
-## Playgrounds
-timeline.xctimeline
-playground.xcworkspace
-
-# Swift Package Manager
-#
-# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
-# Packages/
-.build/
-
-# CocoaPods
-#
-# We recommend against adding the Pods directory to your .gitignore. However
-# you should judge for yourself, the pros and cons are mentioned at:
-# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
-#
-# Pods/
-
-# Carthage
-#
-# Add this line if you want to avoid checking in source code from Carthage dependencies.
-# Carthage/Checkouts
-
-Carthage/Build
-
-# fastlane
-#
-# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
-# screenshots whenever they are needed.
-# For more information about the recommended setup visit:
-# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
-
-fastlane/report.xml
-fastlane/Preview.html
-fastlane/screenshots
-fastlane/test_output
-
-# AppCode
-.idea
-
-TPInAppReceiptTests/MyDevice.swift
-
-.swiftpm/
+DerivedData/
+.swiftpm/config/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc
+Package.resolved
+.swiftpm
+/out
+.claude
diff --git a/.spi.yml b/.spi.yml
new file mode 100644
index 0000000..c9d8ed1
--- /dev/null
+++ b/.spi.yml
@@ -0,0 +1,4 @@
+version: 1
+builder:
+ configs:
+ - documentation_targets: [TPInAppReceipt]
diff --git a/.swift-format b/.swift-format
new file mode 100644
index 0000000..c906083
--- /dev/null
+++ b/.swift-format
@@ -0,0 +1,58 @@
+{
+ "fileScopedDeclarationPrivacy" : {
+ "accessLevel" : "private"
+ },
+ "indentation" : {
+ "spaces" : 4
+ },
+ "indentConditionalCompilationBlocks" : false,
+ "indentSwitchCaseLabels" : false,
+ "lineBreakAroundMultilineExpressionChainComponents" : false,
+ "lineBreakBeforeControlFlowKeywords" : false,
+ "lineBreakBeforeEachArgument" : true,
+ "lineBreakBeforeEachGenericRequirement" : true,
+ "lineLength" : 120,
+ "maximumBlankLines" : 1,
+ "prioritizeKeepingFunctionOutputTogether" : true,
+ "respectsExistingLineBreaks" : true,
+ "rules" : {
+ "AllPublicDeclarationsHaveDocumentation" : false,
+ "AlwaysUseLowerCamelCase" : false,
+ "AmbiguousTrailingClosureOverload" : true,
+ "BeginDocumentationCommentWithOneLineSummary" : false,
+ "DoNotUseSemicolons" : true,
+ "DontRepeatTypeInStaticProperties" : true,
+ "FileScopedDeclarationPrivacy" : true,
+ "FullyIndirectEnum" : true,
+ "GroupNumericLiterals" : true,
+ "IdentifiersMustBeASCII" : true,
+ "NeverForceUnwrap" : false,
+ "NeverUseForceTry" : false,
+ "NeverUseImplicitlyUnwrappedOptionals" : false,
+ "NoAccessLevelOnExtensionDeclaration" : true,
+ "NoAssignmentInExpressions" : true,
+ "NoBlockComments" : true,
+ "NoCasesWithOnlyFallthrough" : true,
+ "NoEmptyTrailingClosureParentheses" : true,
+ "NoLabelsInCasePatterns" : false,
+ "NoLeadingUnderscores" : false,
+ "NoParensAroundConditions" : true,
+ "NoVoidReturnOnFunctionSignature" : true,
+ "OneCasePerLine" : true,
+ "OneVariableDeclarationPerLine" : true,
+ "OnlyOneTrailingClosureArgument" : true,
+ "OrderedImports" : false,
+ "ReturnVoidInsteadOfEmptyTuple" : true,
+ "UseEarlyExits" : true,
+ "UseLetInEveryBoundCaseVariable" : false,
+ "UseShorthandTypeNames" : true,
+ "UseSingleLinePropertyGetter" : false,
+ "UseSynthesizedInitializer" : false,
+ "UseTripleSlashForDocumentationComments" : true,
+ "UseWhereClausesInForLoops" : false,
+ "ValidateDocumentationComments" : false
+ },
+ "spacesAroundRangeFormationOperators" : false,
+ "tabWidth" : 4,
+ "version" : 1
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..c8f0fe8
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,35 @@
+# Changelog
+
+## 4.0.0
+
+### Breaking Changes
+
+- `InAppReceipt` class replaced by `AppReceipt` typealias over `ContentInfo>`
+- Sync validation (`try receipt.verify()`) replaced by async (`await receipt.validate()`)
+- Error types renamed: `IARError` → `AppReceiptError`, `ReceiptValidatorError`
+- `originalAppVersion` is now `String?` (was `String`)
+- `ageRating` is now `String?` (was `String`)
+- Minimum platforms raised: iOS 13+, macOS 10.15+, tvOS 13+
+- Swift tools version: 6.0
+- Removed Objective-C support
+- Removed CocoaPods support
+
+### New
+
+- Full PKCS#7 structure: `ContentInfo`, `SignedData`, `EncapsulatedContentInfo`, `SignerInfo`, `AlgorithmIdentifier`, etc.
+- Composable validation via `@VerifierBuilder` and `ReceiptValidator`
+- X.509 certificate chain verification using swift-certificates
+- Signature verification using swift-crypto
+- New receipt fields: `environment`, `appStoreID`, `transactionDate`, `fulfillmentToolVersion`, `developerID`, `downloadID`, `installerVersionID`
+- `InAppReceiptPayload.Environment` enum (`.production`, `.sandbox`, `.xcode`, `.productionSandbox`)
+- `InAppPurchase.Type` enum (`.consumable`, `.nonConsumable`, `.autoRenewableSubscription`, `.nonRenewingSubscription`)
+- Blocking API via `@_spi(Blocking)`: `local_blocking`, `validate_blocking()`
+- Pluggable decoder via `AppReceiptDecoder.Engine` protocol
+- DocC documentation
+- Privacy manifest (`PrivacyInfo.xcprivacy`)
+- visionOS support
+
+### Dependencies
+
+- Removed: ASN1Swift
+- Added: [swift-asn1](https://github.com/apple/swift-asn1) 1.5.0+, [swift-certificates](https://github.com/apple/swift-certificates) 1.15.1+, [swift-crypto](https://github.com/apple/swift-crypto) 4.2.0+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 20746e1..b839e0a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,13 +1,54 @@
# Contributing to TPInAppReceipt
-### All contributions to TPInAppReceipt are welcome.
+Thank you for your interest in contributing to TPInAppReceipt! This document provides guidelines and instructions for contributing.
-If you want to help developing TPInAppReceipt, please take a look for any opened issue or you can introduce a new functionality.
+## Reporting Bugs
-### Increase unit test coverage
+If you find a bug, please open an issue on [GitHub Issues](https://github.com/tikhop/TPInAppReceipt/issues) with:
-Additional unit test coverage is welcome.
+- A clear, descriptive title
+- Steps to reproduce the issue
+- Expected behavior vs actual behavior
+- Swift version and platform information
+- Minimal code example if possible
-## Issues
+## Suggesting Features
-If TPInAppReceipt doesn't work as you expect, please review [any open issues](https://github.com/tikhop/TPInAppReceipt/issues) before opening a new one.
+Feature requests are welcome! Please open an issue with:
+
+- A clear description of the feature
+- Use cases and motivation
+- Any implementation ideas you may have
+
+## Pull Request Process
+
+1. Fork the repository and create your branch from `master`
+2. Make your changes and ensure tests pass
+3. Add tests for any new functionality
+4. Update documentation if needed
+5. Submit a pull request with a clear description of your changes
+
+### PR Guidelines
+
+- Keep changes focused and atomic
+- Follow existing code patterns
+- Ensure all tests pass before submitting
+
+## Code Style
+
+This project follows Swift conventions. Please reference `.swift-format` for formatting guidelines.
+
+To format all files: `swift format -i . --recursive`
+
+Key points:
+- Keep lines reasonably short
+- Use meaningful variable and function names
+
+## Testing Requirements
+
+All contributions should include appropriate tests:
+
+- Run existing tests with `swift test`
+- Add unit tests for new functionality
+
+Thank you for contributing!
diff --git a/Documentation/UseInObjCProject.md b/Documentation/UseInObjCProject.md
deleted file mode 100644
index 2584132..0000000
--- a/Documentation/UseInObjCProject.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Use TPInAppReceipt in Objective-C project
-
-Installation
-------------
-
-### CocoaPods
-
-To integrate TPInAppReceipt into your Objective-C project using CocoaPods, specify it in your `Podfile`:
-
-```ruby
-platform :ios, '9.0'
-
-target 'YOUR_TARGET' do
-use_frameworks!
-
-pod 'TPInAppReceipt/Objc'
-end
-
-```
-
-Then, run the following command:
-
-```bash
-$ pod install
-```
-
-In any swift file you'd like to use TPInAppReceipt, import the framework with `@import TPInAppReceipt;`.
-
-### Swift Package Manager
-
-To integrate using Apple's Swift package manager, add the following as a dependency to your `Package.swift`:
-
-```swift
-.package(url: "https://github.com/tikhop/TPInAppReceipt.git", .upToNextMajor(from: "3.0.0"))
-```
-
-Then, specify `"TPInAppReceipt-Objc"` as a dependency of the Target in which you wish to use TPInAppReceipt.
-
-Lastly, run the following command:
-```swift
-swift package update
-```
-
-Once you are done with SPM, you can import the framework with `@import TPInAppReceipt_Objc;`.
diff --git a/LICENSE b/LICENSE
index c111a51..2b3ff8a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2016-present Pavel Tikhoneko
+Copyright (c) 2016-Present Pavel Tikhonenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Package.resolved b/Package.resolved
deleted file mode 100644
index c26287d..0000000
--- a/Package.resolved
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "pins" : [
- {
- "identity" : "asn1swift",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/tikhop/ASN1Swift",
- "state" : {
- "revision" : "4e525ca3064fd45751b80c9ae6a2c16ea7b395e9",
- "version" : "1.2.6"
- }
- }
- ],
- "version" : 2
-}
diff --git a/Package.swift b/Package.swift
index fc0e116..e99ce43 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,43 +1,72 @@
-// swift-tools-version:5.9
+// swift-tools-version:6.0
import PackageDescription
+#if canImport(Darwin)
+let privacyManifestExclude: [String] = []
+let privacyManifestResource: [PackageDescription.Resource] = [.copy("Resources/PrivacyInfo.xcprivacy")]
+#else
+// Exclude on other platforms to avoid build warnings.
+let privacyManifestExclude: [String] = ["Resources/PrivacyInfo.xcprivacy"]
+let privacyManifestResource: [PackageDescription.Resource] = []
+#endif
+
+#if canImport(Darwin)
+let httpClientDependency: [Package.Dependency] = []
+let httpClientTargetDependency: [Target.Dependency] = []
+#else
+let httpClientDependency: [Package.Dependency] = [
+ .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.24.0")
+]
+let httpClientTargetDependency: [Target.Dependency] = [
+ .product(name: "AsyncHTTPClient", package: "async-http-client")
+]
+#endif
+
let package = Package(
name: "TPInAppReceipt",
- platforms: [.macOS(.v10_13),
- .iOS(.v12),
- .tvOS(.v12),
- .watchOS("6.2"),
- .visionOS(.v1),
- .macCatalyst(.v13)],
-
+ platforms: [
+ .macOS(.v10_15),
+ .iOS(.v13),
+ .tvOS(.v13),
+ .watchOS("6.2"),
+ .visionOS(.v1),
+ .macCatalyst(.v13),
+ ],
+
products: [
- .library(name: "TPInAppReceipt", targets: ["TPInAppReceipt"]),
- .library(name: "TPInAppReceipt-Objc", targets: ["TPInAppReceipt-Objc"]),
+ .library(name: "TPInAppReceipt", targets: ["TPInAppReceipt"])
],
- dependencies: [
- .package(url: "https://github.com/tikhop/ASN1Swift", .upToNextMajor(from: "1.0.0"))
- ],
+ dependencies: [
+ .package(url: "https://github.com/apple/swift-asn1", .upToNextMajor(from: "1.5.0")),
+ .package(url: "https://github.com/apple/swift-certificates.git", .upToNextMajor(from: "1.15.1")),
+ .package(url: "https://github.com/apple/swift-crypto.git", .upToNextMajor(from: "4.2.0")),
+ ] + httpClientDependency,
targets: [
.target(
name: "TPInAppReceipt",
- dependencies: ["ASN1Swift"],
- path: "Sources",
- exclude: ["Bundle+Private.swift", "Objc/InAppReceipt+Objc.swift"],
- resources: [.process("AppleIncRootCertificate.cer"),
- .process("StoreKitTestCertificate.cer"),
- .process("PrivacyInfo.xcprivacy")]
- ),
- .target(
- name: "TPInAppReceipt-Objc",
- dependencies: ["TPInAppReceipt"],
- path: "Sources/Objc"
- ),
- .testTarget(
- name: "TPInAppReceiptTests",
- dependencies: ["TPInAppReceipt"])
- ],
- swiftLanguageVersions: [.v5]
+ dependencies: [
+ .product(name: "SwiftASN1", package: "swift-asn1"),
+ .product(name: "X509", package: "swift-certificates"),
+ .product(name: "Crypto", package: "swift-crypto"),
+ ] + httpClientTargetDependency,
+ path: "Sources",
+ exclude: privacyManifestExclude,
+ resources: privacyManifestResource + [
+ .process("Resources/AppleIncRootCertificate.cer"),
+ .process("Resources/StoreKitTestCertificate.cer"),
+ ]
+ ),
+ .testTarget(
+ name: "TPInAppReceiptTests",
+ dependencies: [
+ "TPInAppReceipt",
+ .product(name: "SwiftASN1", package: "swift-asn1"),
+ .product(name: "X509", package: "swift-certificates"),
+ .product(name: "Crypto", package: "swift-crypto"),
+ ],
+ path: "Tests",
+ resources: [.copy("Assets")]
+ ),
+ ]
)
-
-
diff --git a/README.md b/README.md
index 75d4969..140763b 100644
--- a/README.md
+++ b/README.md
@@ -2,201 +2,96 @@
-
# TPInAppReceipt

-[](https://cocoapods.org/pods/TPInAppReceipt)
[](https://github.com/apple/swift-package-manager)
-[]()
[](https://raw.githubusercontent.com/tikhop/TPInAppReceipt/master/LICENSE)
-TPInAppReceipt is a lightweight, pure-Swift library for reading and validating Apple In App Purchase Receipt locally.
-
-## Features
-
-- [x] Read all In-App Receipt Attributes
-- [x] Validate In-App Purchase Receipt (Signature, Bundle Version and Identifier, Hash)
-- [x] Determine Eligibility for Introductory Offer
-- [x] Use with StoreKitTest
-- [x] Use in Objective-C projects
-
-Installation
-------------
-
-> Note: [TPInAppReceipt in Objective-C project](https://github.com/tikhop/TPInAppReceipt/blob/master/Documentation/UseInObjCProject.md) - If you want to use TPInAppReceipt in Objective-C project please follow this guide.
-
-### CocoaPods
+Local decoding and validation of Apple App Store receipts. Built on top of Apple's [swift-asn1](https://github.com/apple/swift-asn1), [swift-certificates](https://github.com/apple/swift-certificates), and [swift-crypto](https://github.com/apple/swift-crypto).
-To integrate TPInAppReceipt into your project using CocoaPods, specify it in your `Podfile`:
-
-```ruby
-platform :ios, '12.0'
-
-target 'YOUR_TARGET' do
- use_frameworks!
+```swift
+let receipt = try await AppReceipt.local
- pod 'TPInAppReceipt'
-end
+receipt.bundleIdentifier // "com.example.app"
+receipt.environment // .production, .sandbox, .xcode
+receipt.purchases // [InAppPurchase]
+let result = await receipt.validate()
```
-Then, run the following command:
+## Requirements
-```bash
-$ pod install
-```
+- Swift 6.0+ / Xcode 16+
+- macOS 10.15+ / iOS 13+ / tvOS 13+ / watchOS 6.2+ / visionOS 1+
-In any swift file you'd like to use TPInAppReceipt, import the framework with `import TPInAppReceipt`.
+## Installation
### Swift Package Manager
-To integrate using Apple's Swift package manager, add the following as a dependency to your `Package.swift`:
-
```swift
-.package(url: "https://github.com/tikhop/TPInAppReceipt.git", .upToNextMajor(from: "3.0.0"))
+dependencies: [
+ .package(url: "https://github.com/tikhop/TPInAppReceipt.git", from: "4.0.0")
+]
```
-Then, specify `"TPInAppReceipt"` as a dependency of the Target in which you wish to use TPInAppReceipt.
-
-Lastly, run the following command:
```swift
-swift package update
+.target(
+ name: "YourTarget",
+ dependencies: ["TPInAppReceipt"]
+)
```
-### Requirements
+## Usage
-- iOS 12.0+ / OSX 10.13+
-- Swift 5.9+
-
-Usage
--------------
-
-### Working With a Receipt
-
-The [`InAppReceipt`](https://tikhop.github.io/TPInAppReceipt/Classes/InAppReceipt.html) object encapsulates information about a receipt and the purchases associated with it. To validate In-App Purchase Receipt you must create an `InAppReceipt` object.
-
-#### Initializing Receipt
-
-To create [`InAppReceipt`](https://tikhop.github.io/TPInAppReceipt/Classes/InAppReceipt.html) object you can either provide a raw receipt data or initialize a local receipt.
+### Decoding and Reading
```swift
-do {
- /// Initialize receipt
- let receipt = try InAppReceipt.localReceipt()
- // let receipt = try InAppReceipt() // Returns local receipt
-
- // let receiptData: Data = ...
- // let receipt = try InAppReceipt.receipt(from: receiptData)
-
-} catch {
- print(error)
-}
+import TPInAppReceipt
+// Local receipt
+let receipt = try await AppReceipt.local
+// From raw data
+let receipt = try AppReceipt.receipt(from: data)
```
-#### Validating Receipt
+Access receipt fields, query purchases, check subscriptions and introductory offer eligibility. See for details.
-`TPInAppReceipt` provides a variety of convenience methods for validating In-App Purchase Receipt:
+### Validating
+
+Default validation: certificate chain + signature + hash + metadata.
```swift
+let result = await receipt.validate()
-/// Verify hash
-try? receipt.verifyHash()
-
-/// Verify bundle identifier
-try? receipt.verifyBundleIdentifier()
-
-/// Verify bundle version
-try? receipt.verifyBundleVersion()
-
-/// Verify signature
-try? receipt.verifySignature()
-
-/// Validate all at once
-do {
- try receipt.verify()
-} catch IARError.validationFailed(reason: .hashValidation) {
- // Do smth
-} catch IARError.validationFailed(reason: .bundleIdentifierVerification) {
- // Do smth
-} catch IARError.validationFailed(reason: .signatureValidation) {
- // Do smth
-} catch {
- // Do smth
+switch result {
+case .valid:
+ break
+case .invalid(let error):
+ print(error)
}
-
```
-> NOTE: Apple recommends to perform receipt validation right after your app is launched. For additional security, you may repeat this check periodically while your application is running.
-> NOTE: If validation fails in iOS, try to refresh the receipt first.
+Supports custom validators via `@VerifierBuilder`. See for details.
-#### Determining Eligibility for Introductory Offer
+### Blocking API
-If your App offers introductory pricing for auto-renewable subscriptions, you will need to dispay the correct price, either the intro or regular price.
-The [`InAppReceipt`](https://tikhop.github.io/TPInAppReceipt/Classes/InAppReceipt.html) class provides an interface for determining introductory price eligibility. At the simplest, just provide a `Set` of product identifiers that belong to the same subscription group:
+For contexts where async is not available. See .
```swift
-// Check whether user is eligible for any products within the same subscription group
-var isEligible = receipt.isEligibleForIntroductoryOffer(for: ["com.test.product.bronze", "com.test.product.silver", "com.test.product.gold"])
-```
-
-> Note: To determine if a user is eligible for an introductory offer, you must initialize and validate receipt first and only then check for eligibility.
-
-
-#### Reading Receipt
-
-```swift
-/// Initialize receipt
-let receipt = try! InAppReceipt.localReceipt()
-
-/// Base64 Encoded Receipt
-let base64Receipt = receipt.base64
-
-/// Check whether receipt contains any purchases
-let hasPurchases = receipt.hasPurchases
-
-/// All auto renewable `InAppPurchase`s,
-let purchases: [InAppPurchase] = receipt.autoRenewablePurchases
-
-/// all ACTIVE auto renewable `InAppPurchase`s,
-let activePurchases: [InAppPurchase] = receipt.activeAutoRenewableSubscriptionPurchases
-
-```
-
-#### Useful methods
-
-```swift
-
-// Retrieve Original TransactionIdentifier for Product Name
-receipt.originalTransactionIdentifier(ofProductIdentifier: subscriptionName)
-
-// Retrieve Active Auto Renewable Subscription's Purchases for Product Name and Specific Date
-receipt.activeAutoRenewableSubscriptionPurchases(ofProductIdentifier: subscriptionName, forDate: Date())
-
-// Retrieve All Purchases for Product Name
-receipt.purchases(ofProductIdentifier: subscriptionName)
+@_spi(Blocking) import TPInAppReceipt
+let receipt = try AppReceipt.local_blocking
+let result = receipt.validate_blocking()
```
-#### Refreshing/Requesting Receipt
+## Migrating from v3
-When necessary, use this method to ensure the receipt you are working with is up-to-date.
-
-```swift
-InAppReceipt.refresh { (error) in
- if let err = error
- {
- print(err)
- } else {
- initializeReceipt()
- }
-}
-
-```
+See .
## Essential Reading
+
* [Apple - About Receipt Validation](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Introduction.html)
* [Apple - Receipt Validation Programming Guide](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1)
* [Apple - Validating Receipts Locally](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html)
@@ -204,8 +99,6 @@ InAppReceipt.refresh { (error) in
* [Faisal Bin Ahmed - All the wrong ways to persist in-app purchase status in your macOS app](https://medium.com/@Faisalbin/all-the-wrong-ways-to-persist-in-app-purchase-status-in-your-macos-app-ce6eb9bcb0c3)
* [objc.io - Receipt Validation](https://www.objc.io/issues/17-security/receipt-validation/)
-
-
## License
-TPInAppReceipt is released under an MIT license. See [LICENSE](https://github.com/tikhop/TPInAppReceipt/blob/master/LICENSE) for more information.
+MIT
diff --git a/Sources/AppReceipt.swift b/Sources/AppReceipt.swift
new file mode 100644
index 0000000..46d4c1c
--- /dev/null
+++ b/Sources/AppReceipt.swift
@@ -0,0 +1,144 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+public typealias AppReceipt = ContentInfo>
+
+extension AppReceipt {
+ /// The environment in which the receipt was generated.
+ public var environment: InAppReceiptPayload.Environment {
+ payload.environment
+ }
+
+ /// The app's bundle identifier.
+ ///
+ /// Uniquely identifies the application that generated the receipt.
+ public var bundleIdentifier: String {
+ payload.bundleIdentifier
+ }
+
+ /// The app's version number.
+ ///
+ /// The current version of the app at the time the receipt was generated.
+ public var appVersion: String {
+ payload.appVersion
+ }
+
+ /// The version of the app that was originally purchased.
+ ///
+ /// This represents the version of the app when the user first purchased it.
+ public var originalAppVersion: String? {
+ payload.originalAppVersion
+ }
+
+ /// The date of the app that was originally purchased.
+ ///
+ /// The date when the user first purchased the app.
+ public var originalPurchaseDate: Date? {
+ payload.originalPurchaseDate
+ }
+
+ /// All in-app purchases in this receipt.
+ ///
+ /// Returns an array of ``InAppPurchase`` objects representing all purchases included in the receipt.
+ public var purchases: [InAppPurchase] {
+ payload.purchases
+ }
+
+ /// All auto-renewable subscription purchases in this receipt.
+ ///
+ /// Returns an array of ``InAppPurchase`` objects that are auto-renewable subscriptions.
+ public var autoRenewablePurchases: [InAppPurchase] {
+ purchases.filter { $0.isRenewableSubscription }
+ }
+
+ /// All currently active auto-renewable subscription purchases in this receipt.
+ ///
+ /// Returns an array of ``InAppPurchase`` objects that are auto-renewable subscriptions
+ /// and are currently active as of the current date.
+ public var activeAutoRenewableSubscriptionPurchases: [InAppPurchase] {
+ purchases
+ .filter {
+ $0.isRenewableSubscription && $0.isActiveAutoRenewableSubscription(forDate: Date())
+ }
+ }
+
+ /// The date that the app receipt expires.
+ ///
+ /// For most apps, this is the expiration date of the last auto-renewable subscription.
+ public var expirationDate: Date? {
+ payload.expirationDate
+ }
+
+ /// Indicates whether the receipt contains any purchases.
+ ///
+ /// Returns `true` if any purchases exist, `false` otherwise.
+ public var hasPurchases: Bool {
+ purchases.count > 0
+ }
+
+ /// Indicates whether the receipt contains any active auto-renewable purchases.
+ ///
+ /// Returns `true` if any active auto-renewable subscription purchases exist, `false` otherwise.
+ public var hasActiveAutoRenewablePurchases: Bool {
+ activeAutoRenewableSubscriptionPurchases.count > 0
+ }
+
+ /// The date the receipt was created.
+ ///
+ /// The date and time when the receipt was generated by Apple.
+ public var creationDate: Date {
+ payload.creationDate
+ }
+
+ /// The app's age rating.
+ ///
+ /// A string value representing the age rating for the app.
+ public var ageRating: String? {
+ payload.ageRating
+ }
+}
+
+extension AppReceipt {
+ /// The decoded receipt payload.
+ ///
+ /// Extracts and returns the in-app receipt payload data from the receipt structure.
+ public var payload: InAppReceiptPayload {
+ guard let payload = content.encapContentInfo.eContent else {
+ fatalError("Invalid receipt payload")
+ }
+
+ return payload
+ }
+
+ /// The raw payload data.
+ ///
+ /// Returns the raw bytes of the receipt payload in its original encoded format.
+ public var payloadRawData: Data {
+ guard
+ let payload = content.encapContentInfo.eContentRaw,
+ !payload.bytes.isEmpty
+ else {
+ fatalError("Invalid receipt payload")
+ }
+
+ return Data(payload.bytes)
+ }
+
+ /// Indicates whether the receipt has a valid structure.
+ ///
+ /// Returns `true` if the receipt contains a valid payload, signer information, and certificates,
+ /// `false` otherwise.
+ public var hasValidStructure: Bool {
+ guard content.encapContentInfo.eContent != nil,
+ !content.signerInfos.isEmpty,
+ !content.certificates.isEmpty
+ else {
+ return false
+ }
+
+ return true
+ }
+}
diff --git a/Sources/Blocking/AppReceipt+Blocking.swift b/Sources/Blocking/AppReceipt+Blocking.swift
new file mode 100644
index 0000000..00edc04
--- /dev/null
+++ b/Sources/Blocking/AppReceipt+Blocking.swift
@@ -0,0 +1,144 @@
+import Foundation
+import X509
+
+@_spi(Blocking)
+extension ReceiptValidator {
+ /// Creates a default receipt validator with blocking verification methods.
+ ///
+ /// Initializes a ``ReceiptValidator`` configured with all verifiers
+ /// to perform complete receipt validation in a blocking manner.
+ ///
+ /// - Parameters:
+ /// - rootCertificate: The root certificate data used to validate the certificate chain.
+ /// - deviceIdentifier: The device identifier data for hash verification.
+ /// - Returns: A configured ``ReceiptValidator`` instance ready for receipt validation.
+ static func default_blocking(
+ rootCertificate: Data,
+ deviceIdentifier: Data
+ ) -> ReceiptValidator {
+ ReceiptValidator {
+ SecChainVerifier(rootCertificates: [rootCertificate])
+ SecSignatureVerifier()
+ HashVerifier(deviceIdentifier: deviceIdentifier)
+ MetaVerifier(
+ appVersionProvider: nativeAppVersionProvider,
+ bundleIdentifierProvider: nativeBundleIdentifierProvider
+ )
+ }
+ }
+
+ /// Validates a receipt using blocking verification methods.
+ ///
+ /// Performs synchronous validation of the provided receipt by running it through
+ /// all configured verifiers sequentially.
+ ///
+ /// - Parameter receipt: The receipt object to validate.
+ /// - Returns: A ``VerificationResult`` indicating whether the receipt is valid or invalid.
+ public func validate_blocking(_ receipt: any ReceiptValidatable) -> VerificationResult {
+ for verifier in verifiers {
+ if case let .invalid(error) = verifier.verify(receipt) {
+ return .invalid(error)
+ }
+ }
+
+ return .valid
+ }
+}
+
+@_spi(Blocking)
+extension Bundle {
+ fileprivate static func appleRootCertificateData(testing: Bool) -> Data? {
+ let filename = testing ? "StoreKitTestCertificate.cer" : "AppleIncRootCertificate.cer"
+ guard let url = url(for: filename) else {
+ return nil
+ }
+ return try? Data(contentsOf: url)
+ }
+
+ /// Retrieves the local App Store receipt data.
+ ///
+ /// Loads the receipt file bundled with the application from the App Store receipt URL.
+ ///
+ /// - Returns: The receipt data as ``Data``, or nil if the receipt file cannot be found or loaded.
+ fileprivate func appStoreReceiptData() -> Data? {
+ guard let receiptUrl = appStoreReceiptURL else { return nil }
+ return try? Data(contentsOf: receiptUrl)
+ }
+
+ /// Retrieve local App Store Receipt Data in base64 string
+ ///
+ /// - Returns: ``Data`` object that represents local receipt
+ /// - throws: An error if receipt file not found or ``Data`` can't be created
+ public func appStoreReceiptBase64() -> String? {
+ appStoreReceiptData()?.base64EncodedString()
+ }
+}
+
+@_spi(Blocking)
+extension AppReceipt {
+ /// Retrieves the local App Store receipt from the application bundle.
+ ///
+ /// Loads and parses the receipt file stored locally within the application bundle.
+ ///
+ /// - Returns: An ``AppReceipt`` instance.
+ /// - Throws: ``AppReceiptError/appStoreReceiptNotFound`` if the receipt is not available,
+ /// or ``AppReceiptError/decodingFailed(_:)`` if the receipt cannot be decoded.
+ public static var local_blocking: AppReceipt {
+ get throws(AppReceiptError) {
+ guard let data = Bundle.main.appStoreReceiptData() else {
+ throw AppReceiptError.appStoreReceiptNotFound
+ }
+
+ return try receipt(from: data)
+ }
+ }
+
+ /// Validates the receipt using blocking verification methods.
+ ///
+ /// Performs synchronous validation of this receipt instance.
+ ///
+ /// - Parameters:
+ /// - rootCertificate: Optional custom root certificate data. If not provided, the appropriate
+ /// Apple root certificate is selected based on the receipt's environment.
+ /// - deviceIdentifier: Optional device identifier data. If not provided, the system device
+ /// identifier is used.
+ /// - Returns: A ``VerificationResult`` indicating whether the receipt is valid or invalid.
+ public func validate_blocking(
+ rootCertificate: Data? = nil,
+ deviceIdentifier: Data? = nil
+ ) -> VerificationResult {
+ guard hasValidStructure else {
+ return .invalid(ReceiptValidatorError.invalidReceiptStructure)
+ }
+
+ let root = rootCertificate ?? Bundle.appleRootCertificateData(testing: payload.environment == .xcode)
+
+ guard let root else {
+ return .invalid(ReceiptValidatorError.rootCertificateInvalid(nil))
+ }
+
+ let deviceId = deviceIdentifier ?? DeviceIdentifier.data_blocking
+
+ guard let deviceId else {
+ return .invalid(HashVerificationError.missingDeviceIdentifier)
+ }
+
+ let validator = ReceiptValidator.default_blocking(
+ rootCertificate: root,
+ deviceIdentifier: deviceId
+ )
+ return validator.validate_blocking(self)
+ }
+}
+
+extension ReceiptVerifier {
+ /// Verifies a receipt.
+ ///
+ /// This is a synchronous implementation that is not supported. Use the async version instead.
+ ///
+ /// - Parameter receipt: The receipt object to verify.
+ /// - Returns: Never returns; always raises a fatal error.
+ public func verify(_: any ReceiptValidatable) -> VerificationResult {
+ fatalError("Use async version instead")
+ }
+}
diff --git a/Sources/Bundle+Extension.swift b/Sources/Bundle+Extension.swift
deleted file mode 100644
index b39ab6c..0000000
--- a/Sources/Bundle+Extension.swift
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// Bundle+Extension.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 26.06.2020.
-// Copyright © 2020-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-
-extension Bundle
-{
- /// Appropriate app version for receipt validation
- var appVersion: String?
- {
- #if targetEnvironment(macCatalyst) || os(macOS)
- let dictKey: String = "CFBundleShortVersionString"
- #else
- let dictKey: String = "CFBundleVersion"
- #endif
-
- guard let v = infoDictionary?[dictKey] as? String else
- {
- return nil
- }
-
- return v
- }
-}
diff --git a/Sources/Bundle+Private.swift b/Sources/Bundle+Private.swift
deleted file mode 100644
index 65dc166..0000000
--- a/Sources/Bundle+Private.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// Bundle+Private.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 26.06.2020.
-// Copyright © 2020-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import class Foundation.Bundle
-extension Foundation.Bundle
-{
- static var module: Bundle =
- {
- return Bundle(for: _TPInAppReceipt.self)
- }()
-}
-
-fileprivate class _TPInAppReceipt {}
diff --git a/Sources/Core/AppReceiptField.swift b/Sources/Core/AppReceiptField.swift
new file mode 100644
index 0000000..9e4a8ab
--- /dev/null
+++ b/Sources/Core/AppReceiptField.swift
@@ -0,0 +1,137 @@
+/// The field types present in an app receipt ASN.1 structure.
+public enum AppReceiptField: Int, Sendable {
+ /// The receipt's environment (Xcode, Production, or ProductionSandbox).
+ case environment = 0
+
+ /// The App Store ID
+ case appStoreID = 1
+
+ /// The app's bundle identifier.
+ case bundleIdentifier = 2
+
+ /// The app's version number.
+ case appVersion = 3
+
+ /// An opaque value used to compute the SHA-1 hash during validation.
+ case opaqueValue = 4
+
+ /// The SHA-1 hash used to validate the receipt.
+ case receiptHash = 5
+
+ /// Reserved for future use.
+ case unknown_6 = 6
+
+ /// Reserved for future use.
+ case unknown_7 = 7
+
+ /// The transaction date.
+ case transactionDate = 8
+
+ /// The fulfillment tool version.
+ case fulfillmentToolVersion = 9
+
+ /// The age rating of the app.
+ case ageRating = 10
+
+ /// The developer ID.
+ case developerID = 11
+
+ /// The date when the app receipt was created.
+ case receiptCreationDate = 12
+
+ /// Reserved for future use.
+ case unknown_13 = 13
+
+ /// Reserved for future use.
+ case unknown_14 = 14
+
+ /// The download ID.
+ case downloadID = 15
+
+ /// The installer version ID.
+ case installerVersionID = 16
+
+ /// The receipt for an in-app purchase.
+ case inAppPurchaseReceipt = 17
+
+ /// The date when the app was originally purchased.
+ case originalAppPurchaseDate = 18
+
+ /// The version of the app that was originally purchased.
+ case originalAppVersion = 19
+
+ /// Reserved for future use.
+ case unknown_20 = 20
+
+ /// The date that the app receipt expires.
+ case expirationDate = 21
+
+ /// Reserved for future use.
+ case unknown_25 = 25
+}
+
+/// The field types present in an in-app purchase receipt ASN.1 structure.
+public enum InAppPurchaseReceiptField: Int, Sendable {
+ /// The number of items purchased.
+ case quantity = 1701
+
+ /// The product identifier of the item that was purchased.
+ case productIdentifier = 1702
+
+ /// The transaction identifier of the item that was purchased.
+ case transactionIdentifier = 1703
+
+ /// The date and time that the item was purchased.
+ case purchaseDate = 1704
+
+ /// The transaction identifier of the original purchase.
+ case originalTransactionIdentifier = 1705
+
+ /// The date of the original transaction.
+ case originalPurchaseDate = 1706
+
+ /// The type of in-app product.
+ case productType = 1707
+
+ /// The expiration date for the subscription.
+ case subscriptionExpirationDate = 1708
+
+ /// Unknown field.
+ case unknown_1709 = 1709
+
+ /// Unknown field.
+ case unknown_1710 = 1710
+
+ /// The primary key for identifying subscription purchases.
+ case webOrderLineItemID = 1711
+
+ /// The time and date of the cancellation.
+ case cancellationDate = 1712
+
+ /// Whether the subscription is in the free trial period.
+ case subscriptionTrialPeriod = 1713
+
+ /// Unknown field.
+ case unknown_1714 = 1714
+
+ /// Unknown field.
+ case unknown_1715 = 1715
+
+ /// Unknown field.
+ case unknown_1716 = 1716
+
+ /// Unknown field.
+ case unknown_1717 = 1717
+
+ /// Unknown field.
+ case unknown_1718 = 1718
+
+ /// Whether the subscription is in the introductory price period.
+ case subscriptionIntroductoryPricePeriod = 1719
+
+ /// The identifier of the subscription offer the user redeemed.
+ case promotionalOfferIdentifier = 1721
+
+ /// Unknown field.
+ case unknown_1722 = 1722
+}
diff --git a/Sources/Core/InAppPurchase.swift b/Sources/Core/InAppPurchase.swift
new file mode 100644
index 0000000..507a8af
--- /dev/null
+++ b/Sources/Core/InAppPurchase.swift
@@ -0,0 +1,132 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+/// A record of an in-app purchase transaction.
+public struct InAppPurchase: Sendable, Hashable {
+ /// The type of in-app purchase product.
+ public enum `Type`: Int32, Sendable {
+ /// An unknown in-app purchase type.
+ case unknown = -1
+
+ /// A non-consumable in-app purchase.
+ case nonConsumable
+
+ /// A consumable in-app purchase.
+ case consumable
+
+ /// A non-renewing subscription.
+ case nonRenewingSubscription
+
+ /// An auto-renewable subscription.
+ case autoRenewableSubscription
+ }
+
+ /// The product identifier of the item that was purchased.
+ public let productIdentifier: String
+
+ /// The type of in-app product.
+ public let productType: Type
+
+ /// The transaction identifier of the item that was purchased.
+ public let transactionIdentifier: String
+
+ /// The transaction identifier of the original purchase.
+ public let originalTransactionIdentifier: String
+
+ /// The date and time that the item was purchased.
+ public let purchaseDate: Date
+
+ /// The date of the original transaction.
+ ///
+ /// Returns `nil` when testing with StoreKitTest.
+ public let originalPurchaseDate: Date?
+
+ /// The expiration date for the subscription.
+ ///
+ /// Only present for auto-renewable subscription receipts.
+ public let subscriptionExpirationDate: Date?
+
+ /// The date Apple customer support canceled a transaction or the date of an upgrade transaction.
+ ///
+ /// Present for auto-renewable subscriptions, non-consumable products, and non-renewing subscriptions
+ /// when the transaction has been canceled or refunded. Returns `nil` if the purchase has not been
+ /// canceled or refunded.
+ public let cancellationDate: Date?
+
+ /// Whether the subscription is in the free trial period.
+ public let subscriptionTrialPeriod: Bool
+
+ /// Whether the subscription is in the introductory price period.
+ public let subscriptionIntroductoryPricePeriod: Bool
+
+ /// The primary key for identifying subscription purchases.
+ ///
+ /// This value is a unique ID that identifies purchase events across devices,
+ /// including subscription renewal purchase events.
+ public let webOrderLineItemID: Int?
+
+ /// The identifier of the subscription offer the user redeemed.
+ public let promotionalOfferIdentifier: String?
+
+ /// The number of items purchased.
+ public let quantity: Int
+
+ /// Creates an in-app purchase record with the specified values.
+ public init(
+ productIdentifier: String,
+ productType: Type,
+ transactionIdentifier: String,
+ originalTransactionIdentifier: String,
+ purchaseDate: Date,
+ originalPurchaseDate: Date?,
+ subscriptionExpirationDate: Date?,
+ cancellationDate: Date?,
+ subscriptionTrialPeriod: Bool,
+ subscriptionIntroductoryPricePeriod: Bool,
+ webOrderLineItemID: Int?,
+ promotionalOfferIdentifier: String?,
+ quantity: Int
+ ) {
+ self.productIdentifier = productIdentifier
+ self.productType = productType
+ self.transactionIdentifier = transactionIdentifier
+ self.originalTransactionIdentifier = originalTransactionIdentifier
+ self.purchaseDate = purchaseDate
+ self.originalPurchaseDate = originalPurchaseDate
+ self.subscriptionExpirationDate = subscriptionExpirationDate
+ self.cancellationDate = cancellationDate
+ self.subscriptionTrialPeriod = subscriptionTrialPeriod
+ self.subscriptionIntroductoryPricePeriod = subscriptionIntroductoryPricePeriod
+ self.webOrderLineItemID = webOrderLineItemID
+ self.promotionalOfferIdentifier = promotionalOfferIdentifier
+ self.quantity = quantity
+ }
+}
+
+extension InAppPurchase {
+ /// A Boolean value indicating whether the purchase is a renewable subscription.
+ public var isRenewableSubscription: Bool {
+ subscriptionExpirationDate != nil
+ }
+
+ /// Returns whether the auto-renewable subscription is active for the specified date.
+ ///
+ /// - Parameter date: The date to check subscription activity.
+ /// - Returns: `true` if the subscription is active on the given date, `false` otherwise.
+ public func isActiveAutoRenewableSubscription(forDate date: Date) -> Bool {
+ assert(isRenewableSubscription, "\(productIdentifier) is not an auto-renewable subscription.")
+
+ if cancellationDate != nil {
+ return false
+ }
+
+ guard let expirationDate = subscriptionExpirationDate else {
+ return false
+ }
+
+ return date >= purchaseDate && date < expirationDate
+ }
+}
diff --git a/Sources/Core/PCKS7/AlgorithmIdentifier.swift b/Sources/Core/PCKS7/AlgorithmIdentifier.swift
new file mode 100644
index 0000000..0fe3194
--- /dev/null
+++ b/Sources/Core/PCKS7/AlgorithmIdentifier.swift
@@ -0,0 +1,66 @@
+import SwiftASN1
+
+/// An algorithm identifier with optional parameters.
+///
+/// AlgorithmIdentifier specifies an algorithm and any necessary parameters for its use.
+public struct AlgorithmIdentifier: Hashable, Sendable {
+ /// The object identifier of the algorithm.
+ public var algorithm: ASN1ObjectIdentifier
+
+ /// The algorithm parameters, if any.
+ public var parameters: ASN1Any?
+
+ /// Creates an AlgorithmIdentifier with the specified algorithm and parameters.
+ @inlinable
+ public init(algorithm: ASN1ObjectIdentifier, parameters: ASN1Any?) {
+ self.algorithm = algorithm
+ self.parameters = parameters
+ }
+}
+
+// MARK: - Algorithm Identifier Statics
+
+extension AlgorithmIdentifier {
+ public static let sha1WithRSAEncryption = AlgorithmIdentifier(
+ algorithm: .AlgorithmIdentifier.sha1WithRSAEncryption,
+ parameters: try! ASN1Any(erasing: ASN1Null())
+ )
+
+ public static let sha256WithRSAEncryption = AlgorithmIdentifier(
+ algorithm: .AlgorithmIdentifier.sha256WithRSAEncryption,
+ parameters: try! ASN1Any(erasing: ASN1Null())
+ )
+
+ public static let sha1 = AlgorithmIdentifier(
+ algorithm: .AlgorithmIdentifier.sha1,
+ parameters: try! ASN1Any(erasing: ASN1Null())
+ )
+
+ public static let sha256 = AlgorithmIdentifier(
+ algorithm: .AlgorithmIdentifier.sha256,
+ parameters: try! ASN1Any(erasing: ASN1Null())
+ )
+}
+
+extension AlgorithmIdentifier: CustomStringConvertible {
+ public var description: String {
+ switch self {
+ case .sha1WithRSAEncryption:
+ return "sha1WithRSAEncryption"
+ case .sha256WithRSAEncryption:
+ return "sha256WithRSAEncryption"
+ case .sha1:
+ return "sha1"
+ case .sha256:
+ return "sha256"
+ default:
+ return "AlgorithmIdentifier(\(algorithm) - \(String(reflecting: parameters)))"
+ }
+ }
+}
+
+extension ASN1ObjectIdentifier.AlgorithmIdentifier {
+ public static let sha1WithRSAEncryption: ASN1ObjectIdentifier = [1, 2, 840, 113_549, 1, 1, 5]
+ public static let sha1: ASN1ObjectIdentifier = [1, 3, 14, 3, 2, 26]
+ public static let sha256: ASN1ObjectIdentifier = [2, 16, 840, 1, 101, 3, 4, 2, 1]
+}
diff --git a/Sources/Core/PCKS7/Attribute.swift b/Sources/Core/PCKS7/Attribute.swift
new file mode 100644
index 0000000..322312f
--- /dev/null
+++ b/Sources/Core/PCKS7/Attribute.swift
@@ -0,0 +1,19 @@
+import SwiftASN1
+
+/// A PKCS#7 Attribute structure.
+///
+/// An attribute contains a type identifier and associated values.
+public struct Attribute: Hashable, Sendable {
+ /// The object identifier of the attribute type.
+ public var attrType: ASN1ObjectIdentifier
+
+ /// The attribute values.
+ public var attrValues: [ASN1Any]
+
+ /// Creates an Attribute with the specified type and values.
+ @inlinable
+ public init(attrType: ASN1ObjectIdentifier, attrValues: [ASN1Any]) {
+ self.attrType = attrType
+ self.attrValues = attrValues
+ }
+}
diff --git a/Sources/Core/PCKS7/ContentInfo.swift b/Sources/Core/PCKS7/ContentInfo.swift
new file mode 100644
index 0000000..1a79afe
--- /dev/null
+++ b/Sources/Core/PCKS7/ContentInfo.swift
@@ -0,0 +1,22 @@
+import SwiftASN1
+
+/// A PKCS#7 ContentInfo structure.
+///
+/// ContentInfo is a simple wrapper that associates content with a content type identifier.
+public struct ContentInfo: Sendable {
+ /// A type alias for the content type identifier.
+ public typealias ContentType = ASN1ObjectIdentifier
+
+ /// The object identifier of the content type.
+ public let contentType: ContentType
+
+ /// The content data.
+ public let content: D
+
+ /// Creates a ContentInfo with the specified type and content.
+ @inlinable
+ public init(contentType: ContentType, content: D) {
+ self.contentType = contentType
+ self.content = content
+ }
+}
diff --git a/Sources/Core/PCKS7/EncapsulatedContentInfo.swift b/Sources/Core/PCKS7/EncapsulatedContentInfo.swift
new file mode 100644
index 0000000..97f73d4
--- /dev/null
+++ b/Sources/Core/PCKS7/EncapsulatedContentInfo.swift
@@ -0,0 +1,27 @@
+import SwiftASN1
+
+/// A PKCS#7 EncapsulatedContentInfo structure.
+///
+/// EncapsulatedContentInfo contains the type and value of the content being protected.
+public struct EncapsulatedContentInfo: Hashable, Sendable {
+ /// The object identifier of the content type.
+ public var eContentType: ASN1ObjectIdentifier
+
+ /// The decoded content.
+ public var eContent: C?
+
+ /// The raw DER-encoded content.
+ public var eContentRaw: ASN1OctetString?
+
+ /// Creates an EncapsulatedContentInfo with the specified type and content.
+ @inlinable
+ public init(
+ eContentType: ASN1ObjectIdentifier,
+ eContent: C? = nil,
+ eContentRaw: ASN1OctetString? = nil
+ ) {
+ self.eContentType = eContentType
+ self.eContent = eContent
+ self.eContentRaw = eContentRaw
+ }
+}
diff --git a/Sources/Core/PCKS7/IssuerAndSerialNumber.swift b/Sources/Core/PCKS7/IssuerAndSerialNumber.swift
new file mode 100644
index 0000000..56c6a60
--- /dev/null
+++ b/Sources/Core/PCKS7/IssuerAndSerialNumber.swift
@@ -0,0 +1,22 @@
+import X509
+
+/// The issuer and serial number of a certificate.
+///
+/// IssuerAndSerialNumber uniquely identifies a certificate by its issuer's distinguished name and serial number.
+public struct IssuerAndSerialNumber: Hashable, Sendable {
+ /// The issuer's distinguished name.
+ public var issuer: DistinguishedName
+
+ /// The certificate serial number.
+ public var serialNumber: Certificate.SerialNumber
+
+ /// Creates an IssuerAndSerialNumber with the specified values.
+ @inlinable
+ public init(
+ issuer: DistinguishedName,
+ serialNumber: Certificate.SerialNumber
+ ) {
+ self.issuer = issuer
+ self.serialNumber = serialNumber
+ }
+}
diff --git a/Sources/Core/PCKS7/SignedData.swift b/Sources/Core/PCKS7/SignedData.swift
new file mode 100644
index 0000000..4d3704e
--- /dev/null
+++ b/Sources/Core/PCKS7/SignedData.swift
@@ -0,0 +1,49 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+import X509
+
+/// A PKCS#7 SignedData structure.
+///
+/// SignedData is the ASN.1 structure defined in RFC 2315 that contains signed content,
+/// certificates, and signer information.
+public struct SignedData: Hashable, Sendable {
+ /// The version of the SignedData structure.
+ public var version: Version
+
+ /// The digest algorithms used by the signers.
+ public var digestAlgorithms: [AlgorithmIdentifier]
+
+ /// The content that is signed.
+ public var encapContentInfo: EncapsulatedContentInfo
+
+ /// The X.509 certificates used for validation.
+ public var certificates: [Certificate]
+
+ /// The raw DER-encoded certificate data.
+ public var certificatesRaw: [Data]
+
+ /// The signer information and signatures.
+ public var signerInfos: [SignerInfo]
+
+ /// Creates a SignedData structure with the specified values.
+ @inlinable
+ public init(
+ version: Version,
+ digestAlgorithms: [AlgorithmIdentifier],
+ encapContentInfo: EncapsulatedContentInfo,
+ certificates: [Certificate],
+ certificatesRaw: [Data] = [],
+ signerInfos: [SignerInfo]
+ ) {
+ self.version = version
+ self.digestAlgorithms = digestAlgorithms
+ self.encapContentInfo = encapContentInfo
+ self.certificates = certificates
+ self.certificatesRaw = certificatesRaw
+ self.signerInfos = signerInfos
+ }
+}
diff --git a/Sources/Core/PCKS7/SignerIdentifier.swift b/Sources/Core/PCKS7/SignerIdentifier.swift
new file mode 100644
index 0000000..e71d871
--- /dev/null
+++ b/Sources/Core/PCKS7/SignerIdentifier.swift
@@ -0,0 +1,18 @@
+import X509
+
+/// An identifier for a signer in a PKCS#7 SignerInfo structure.
+///
+/// SignerIdentifier can identify a signer either by issuer and serial number or by subject key identifier.
+public enum SignerIdentifier: Hashable, Sendable {
+ /// The issuer and serial number of the signer's certificate.
+ case issuerAndSerialNumber(IssuerAndSerialNumber)
+
+ /// The subject key identifier of the signer's certificate.
+ case subjectKeyIdentifier(SubjectKeyIdentifier)
+
+ /// Creates a signer identifier from a certificate using its issuer and serial number.
+ @inlinable
+ public init(issuerAndSerialNumber certificate: Certificate) {
+ self = .issuerAndSerialNumber(.init(issuer: certificate.issuer, serialNumber: certificate.serialNumber))
+ }
+}
diff --git a/Sources/Core/PCKS7/SignerInfo.swift b/Sources/Core/PCKS7/SignerInfo.swift
new file mode 100644
index 0000000..a38b332
--- /dev/null
+++ b/Sources/Core/PCKS7/SignerInfo.swift
@@ -0,0 +1,53 @@
+import SwiftASN1
+
+/// A PKCS#7 SignerInfo structure.
+///
+/// SignerInfo contains the signature and signer information for a piece of signed data.
+public struct SignerInfo: Hashable, Sendable {
+ /// Errors that occur in SignerInfo operations.
+ public enum Error: Swift.Error {
+ /// The version and signer identifier are incompatible.
+ case versionAndSignerIdentifierMismatch(String)
+ }
+
+ /// The version of the SignerInfo structure.
+ public var version: Version
+
+ /// The identifier of the signer.
+ public var signerIdentifier: SignerIdentifier
+
+ /// The digest algorithm used by the signer.
+ public var digestAlgorithm: AlgorithmIdentifier
+
+ /// The signed attributes.
+ public var signedAttrs: [Attribute]?
+
+ /// The signature algorithm.
+ public var signatureAlgorithm: AlgorithmIdentifier
+
+ /// The signature value.
+ public var signature: ASN1OctetString
+
+ /// The unsigned attributes.
+ public var unsignedAttrs: [Attribute]?
+
+ /// Creates a SignerInfo with the specified values.
+ @inlinable
+ public init(
+ version: Version,
+ signerIdentifier: SignerIdentifier,
+ digestAlgorithm: AlgorithmIdentifier,
+ signedAttrs: [Attribute]? = nil,
+ signatureAlgorithm: AlgorithmIdentifier,
+ signature: ASN1OctetString,
+ unsignedAttrs: [Attribute]? = nil
+ ) {
+ self.version = version
+ self.signerIdentifier = signerIdentifier
+ self.digestAlgorithm = digestAlgorithm
+ self.signedAttrs = signedAttrs
+ self.signatureAlgorithm = signatureAlgorithm
+ self.signature = signature
+ self.unsignedAttrs = unsignedAttrs
+ }
+}
diff --git a/Sources/Core/PCKS7/Time.swift b/Sources/Core/PCKS7/Time.swift
new file mode 100644
index 0000000..7ba0e3c
--- /dev/null
+++ b/Sources/Core/PCKS7/Time.swift
@@ -0,0 +1,66 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+import SwiftASN1
+
+/// A time representation in ASN.1 format.
+///
+/// Time can be represented in either UTC format (for dates up to 2050) or generalized format (for all dates).
+public enum Time: Hashable, Sendable {
+ /// A UTC time representation.
+ case utcTime(UTCTime)
+
+ /// A generalized time representation.
+ case generalTime(GeneralizedTime)
+}
+
+extension GeneralizedTime {
+ @inlinable
+ init(_ time: Time) {
+ switch time {
+ case let .generalTime(t):
+ self = t
+ case let .utcTime(t):
+ // This can never throw, all valid UTCTimes are valid GeneralizedTimes
+ self = try! GeneralizedTime(
+ year: t.year,
+ month: t.month,
+ day: t.day,
+ hours: t.hours,
+ minutes: t.minutes,
+ seconds: t.seconds,
+ fractionalSeconds: 0
+ )
+ }
+ }
+
+ @inlinable
+ init(_ components: (year: Int, month: Int, day: Int, hours: Int, minutes: Int, seconds: Int)) throws {
+ try self.init(
+ year: components.year,
+ month: components.month,
+ day: components.day,
+ hours: components.hours,
+ minutes: components.minutes,
+ seconds: components.seconds,
+ fractionalSeconds: 0.0
+ )
+ }
+}
+
+extension UTCTime {
+ @inlinable
+ init(_ components: (year: Int, month: Int, day: Int, hours: Int, minutes: Int, seconds: Int)) throws {
+ try self.init(
+ year: components.year,
+ month: components.month,
+ day: components.day,
+ hours: components.hours,
+ minutes: components.minutes,
+ seconds: components.seconds
+ )
+ }
+}
diff --git a/Sources/Core/PCKS7/Version.swift b/Sources/Core/PCKS7/Version.swift
new file mode 100644
index 0000000..7e61a60
--- /dev/null
+++ b/Sources/Core/PCKS7/Version.swift
@@ -0,0 +1,34 @@
+/// A CMS/PKCS#7 version number.
+public struct Version: RawRepresentable, Hashable, Sendable {
+ /// The raw version value.
+ public var rawValue: Int
+
+ /// Creates a version with the specified raw value.
+ public init(rawValue: Int) {
+ self.rawValue = rawValue
+ }
+
+ /// Version 0.
+ public static let v0 = Self(rawValue: 0)
+
+ /// Version 1.
+ public static let v1 = Self(rawValue: 1)
+
+ /// Version 2.
+ public static let v2 = Self(rawValue: 2)
+
+ /// Version 3.
+ public static let v3 = Self(rawValue: 3)
+
+ /// Version 4.
+ public static let v4 = Self(rawValue: 4)
+
+ /// Version 5.
+ public static let v5 = Self(rawValue: 5)
+}
+
+extension Version: CustomStringConvertible {
+ public var description: String {
+ "CMSv\(rawValue)"
+ }
+}
diff --git a/Sources/Core/ReceiptPayload.swift b/Sources/Core/ReceiptPayload.swift
new file mode 100644
index 0000000..8892c95
--- /dev/null
+++ b/Sources/Core/ReceiptPayload.swift
@@ -0,0 +1,133 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+/// The payload of an app receipt extracted from a PKCS#7 container.
+///
+/// `InAppReceiptPayload` contains all the decoded information from an app receipt,
+/// including app details, purchase history, and validation data.
+public struct InAppReceiptPayload: Sendable, Hashable {
+ public enum Environment: String, Hashable, Sendable {
+ case production = "Production"
+ case productionSandbox = "ProductionSandbox"
+ case sandbox = "Sandbox"
+ case xcode = "Xcode"
+ case unknown = "Unknown"
+
+ public init(rawValue: String) {
+ switch rawValue {
+ case "Production":
+ self = .production
+ case "ProductionSandbox":
+ self = .productionSandbox
+ case "Sandbox":
+ self = .sandbox
+ case "Xcode":
+ self = .xcode
+ default:
+ self = .unknown
+ }
+ }
+ }
+
+ /// The in-app purchase receipts.
+ public let purchases: [InAppPurchase]
+
+ /// The app's bundle identifier.
+ public let bundleIdentifier: String
+
+ /// The app's version number.
+ public let appVersion: String
+
+ /// The version of the app that was originally purchased.
+ public let originalAppVersion: String?
+
+ /// The date when the app was originally purchased.
+ public let originalPurchaseDate: Date?
+
+ /// The date that the app receipt expires.
+ ///
+ /// This key is only present for apps purchased through the Volume Purchase Program.
+ public let expirationDate: Date?
+
+ /// The app's bundle identifier as raw data.
+ ///
+ /// Used to validate the receipt.
+ public let bundleIdentifierData: Data
+
+ /// An opaque value used, with other data, to compute the SHA-1 hash.
+ public let opaqueValue: Data
+
+ /// The SHA-1 hash, used to validate the receipt.
+ public let receiptHash: Data
+
+ /// The date when the app receipt was created.
+ public let creationDate: Date
+
+ /// The age rating of the app.
+ public let ageRating: String?
+
+ /// The receipt's environment.
+ public let environment: Environment
+
+ /// The Apple ID used to purchase the app.
+ public let appStoreID: Int?
+
+ /// The transaction date.
+ public let transactionDate: Date?
+
+ /// The fulfillment tool version.
+ public let fulfillmentToolVersion: Int?
+
+ /// The developer ID.
+ public let developerID: Int?
+
+ /// The download ID.
+ public let downloadID: Int?
+
+ /// The installer version ID.
+ public let installerVersionID: Int?
+
+ /// Creates an app receipt payload with the specified values.
+ public init(
+ bundleIdentifier: String,
+ appVersion: String,
+ originalAppVersion: String?,
+ originalPurchaseDate: Date?,
+ purchases: [InAppPurchase],
+ expirationDate: Date?,
+ bundleIdentifierData: Data,
+ opaqueValue: Data,
+ receiptHash: Data,
+ creationDate: Date,
+ ageRating: String?,
+ environment: Environment,
+ appStoreID: Int? = nil,
+ transactionDate: Date? = nil,
+ fulfillmentToolVersion: Int? = nil,
+ developerID: Int? = nil,
+ downloadID: Int? = nil,
+ installerVersionID: Int? = nil
+ ) {
+ self.bundleIdentifier = bundleIdentifier
+ self.appVersion = appVersion
+ self.originalAppVersion = originalAppVersion
+ self.originalPurchaseDate = originalPurchaseDate
+ self.purchases = purchases
+ self.expirationDate = expirationDate
+ self.bundleIdentifierData = bundleIdentifierData
+ self.opaqueValue = opaqueValue
+ self.receiptHash = receiptHash
+ self.creationDate = creationDate
+ self.ageRating = ageRating
+ self.environment = environment
+ self.appStoreID = appStoreID
+ self.transactionDate = transactionDate
+ self.fulfillmentToolVersion = fulfillmentToolVersion
+ self.developerID = developerID
+ self.downloadID = downloadID
+ self.installerVersionID = installerVersionID
+ }
+}
diff --git a/Sources/Data+Extension.swift b/Sources/Data+Extension.swift
deleted file mode 100644
index 134c4d1..0000000
--- a/Sources/Data+Extension.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// Data+Extension.swift
-// TPReceiptValidator
-//
-// Created by Pavel Tikhonenko on 29/09/16.
-// Copyright © 2016-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-
-extension Data
-{
- public var bytes: Array
- {
- return Array(self)
- }
-}
-
-
diff --git a/Sources/Date+Extension.swift b/Sources/Date+Extension.swift
deleted file mode 100644
index d24713c..0000000
--- a/Sources/Date+Extension.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// Date+Extension.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 01/10/16.
-// Copyright © 2016-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-
-public extension Date
-{
- func rfc3339date(fromString string: String) -> Date?
- {
- return string.rfc3339date()
- }
-}
-
-public extension String
-{
- func rfc3339date() -> Date?
- {
- let date = rfc3339DateFormatter.date(from: self)
- return date
- }
-}
-
-fileprivate var rfc3339DateFormatter: ISO8601DateFormatter = {
- let formatter = ISO8601DateFormatter()
- formatter.formatOptions = .withInternetDateTime
- formatter.timeZone = TimeZone(abbreviation: "UTC")
- return formatter
-}()
diff --git a/Sources/Decoder/AppReceiptDecoder.swift b/Sources/Decoder/AppReceiptDecoder.swift
new file mode 100644
index 0000000..b5d2d11
--- /dev/null
+++ b/Sources/Decoder/AppReceiptDecoder.swift
@@ -0,0 +1,40 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+/// A decoder for app receipts.
+///
+/// `AppReceiptDecoder` uses a decoding engine to extract receipt data.
+public final class AppReceiptDecoder: Sendable {
+ /// A protocol for decoding app receipt data.
+ ///
+ /// Implement this protocol to provide custom receipt decoding logic.
+ public protocol Engine: Sendable {
+ /// Decodes an app receipt.
+ ///
+ /// - Parameter data: The receipt data to decode.
+ /// - Returns: The decoded app receipt.
+ /// - Throws: ``AppReceiptError/decodingFailed(_:)`` if the data is invalid.
+ func decode(from data: Data) throws -> AppReceipt
+ }
+
+ let engine: Engine
+
+ /// Creates a decoder with the specified decoding engine.
+ ///
+ /// - Parameter engine: The engine to use for decoding.
+ public init(engine: Engine) {
+ self.engine = engine
+ }
+
+ /// Decodes an app receipt.
+ ///
+ /// - Parameter data: The receipt data to decode.
+ /// - Returns: The decoded app receipt.
+ /// - Throws: ``AppReceiptError/decodingFailed(_:)`` if the data is invalid.
+ func decode(from data: Data) throws -> AppReceipt {
+ try engine.decode(from: data)
+ }
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/AlgorithmIdentifier+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/AlgorithmIdentifier+SwiftASN1.swift
new file mode 100644
index 0000000..48fe9ed
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/AlgorithmIdentifier+SwiftASN1.swift
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCertificates open source project
+//
+// Copyright (c) 2022 Apple Inc. and the SwiftCertificates project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+import SwiftASN1
+
+extension AlgorithmIdentifier: DERSerializable, DERImplicitlyTaggable, BERImplicitlyTaggable {
+ @inlinable
+ public static var defaultIdentifier: ASN1Identifier {
+ .sequence
+ }
+
+ @inlinable
+ public init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ // The AlgorithmIdentifier block looks like this.
+ //
+ // AlgorithmIdentifier ::= SEQUENCE {
+ // algorithm OBJECT IDENTIFIER,
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ // }
+ self = try DER.sequence(rootNode, identifier: identifier) { nodes in
+ let algorithmOID = try ASN1ObjectIdentifier(derEncoded: &nodes)
+
+ let parameters = nodes.next().map { ASN1Any(derEncoded: $0) }
+
+ return .init(algorithm: algorithmOID, parameters: parameters)
+ }
+ }
+
+ @inlinable
+ public init(berEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ self = try .init(derEncoded: rootNode, withIdentifier: identifier)
+ }
+
+ @inlinable
+ public func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(self.algorithm)
+ if let parameters = self.parameters {
+ try coder.serialize(parameters)
+ }
+ }
+ }
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/Attribute+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/Attribute+SwiftASN1.swift
new file mode 100644
index 0000000..2d852cd
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/Attribute+SwiftASN1.swift
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCertificates open source project
+//
+// Copyright (c) 2023 Apple Inc. and the SwiftCertificates project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import SwiftASN1
+
+/// ``CMSAttribute`` is defined in ASN.1 as:
+/// ```
+/// Attribute ::= SEQUENCE {
+/// attrType OBJECT IDENTIFIER,
+/// attrValues SET OF AttributeValue }
+///
+/// AttributeValue ::= ANY
+/// ```
+extension Attribute: DERImplicitlyTaggable, BERImplicitlyTaggable {
+ @inlinable
+ public static var defaultIdentifier: ASN1Identifier {
+ .sequence
+ }
+
+ @inlinable
+ public init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ self = try DER.sequence(rootNode, identifier: identifier) { nodes in
+ let attrType = try ASN1ObjectIdentifier(derEncoded: &nodes)
+ let attrValues = try DER.set(of: ASN1Any.self, identifier: .set, nodes: &nodes)
+
+ return .init(attrType: attrType, attrValues: attrValues)
+ }
+ }
+
+ @inlinable
+ public init(berEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ self = try BER.sequence(rootNode, identifier: identifier) { nodes in
+ let attrType = try ASN1ObjectIdentifier(berEncoded: &nodes)
+ let attrValues = try BER.set(of: ASN1Any.self, identifier: .set, nodes: &nodes)
+
+ return .init(attrType: attrType, attrValues: attrValues)
+ }
+ }
+
+ @inlinable
+ public func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(self.attrType)
+ try coder.serializeSetOf(self.attrValues)
+ }
+ }
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/ContentInfo+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/ContentInfo+SwiftASN1.swift
new file mode 100644
index 0000000..5d8c20e
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/ContentInfo+SwiftASN1.swift
@@ -0,0 +1,45 @@
+import SwiftASN1
+
+/// ContentInfo ::= SEQUENCE {
+/// contentType ContentType,
+/// content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
+///
+/// ContentType ::= OBJECT IDENTIFIER
+
+extension ContentInfo: DERParseable where D: BERImplicitlyTaggable {}
+extension ContentInfo: DERSerializable where D: BERImplicitlyTaggable {}
+extension ContentInfo: DERImplicitlyTaggable where D: BERImplicitlyTaggable {}
+extension ContentInfo: BERParseable where D: BERImplicitlyTaggable {}
+extension ContentInfo: BERSerializable where D: BERImplicitlyTaggable {}
+extension ContentInfo: BERImplicitlyTaggable where D: BERImplicitlyTaggable {
+ @inlinable
+ public static var defaultIdentifier: ASN1Identifier {
+ .sequence
+ }
+
+ @inlinable
+ public init(berEncoded node: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ self = try BER.sequence(node, identifier: identifier) { nodes in
+ let contentType = try ContentType(berEncoded: &nodes)
+
+ let content = try BER.explicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { node in
+ try D(berEncoded: node)
+ }
+
+ return .init(contentType: contentType, content: content)
+ }
+ }
+
+ @inlinable
+ public init(derEncoded node: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ try self.init(berEncoded: node, withIdentifier: identifier)
+ }
+
+ @inlinable
+ public func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(self.contentType)
+ try coder.serialize(self.content)
+ }
+ }
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/EncapsulatedContentInfo+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/EncapsulatedContentInfo+SwiftASN1.swift
new file mode 100644
index 0000000..8b3368e
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/EncapsulatedContentInfo+SwiftASN1.swift
@@ -0,0 +1,58 @@
+import SwiftASN1
+
+/// ``CMSEncapsulatedContentInfo`` is defined in ASN.1 as:
+/// ```
+/// EncapsulatedContentInfo ::= SEQUENCE {
+/// eContentType ContentType,
+/// eContent [0] EXPLICIT OCTET STRING OPTIONAL }
+/// ContentType ::= OBJECT IDENTIFIER
+/// ```
+///
+extension EncapsulatedContentInfo: DERParseable where C: BERImplicitlyTaggable {}
+extension EncapsulatedContentInfo: DERSerializable where C: BERImplicitlyTaggable {}
+extension EncapsulatedContentInfo: DERImplicitlyTaggable where C: BERImplicitlyTaggable {}
+extension EncapsulatedContentInfo: BERParseable where C: BERImplicitlyTaggable {}
+extension EncapsulatedContentInfo: BERSerializable where C: BERImplicitlyTaggable {}
+extension EncapsulatedContentInfo: BERImplicitlyTaggable where C: BERImplicitlyTaggable {
+ @inlinable
+ public static var defaultIdentifier: ASN1Identifier {
+ .sequence
+ }
+
+ @inlinable
+ public init(berEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ self = try BER.sequence(rootNode, identifier: identifier) { nodes in
+ let eContentType = try ASN1ObjectIdentifier(berEncoded: &nodes)
+ let eContentRaw = try BER.optionalExplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) {
+ node in
+ try ASN1OctetString(berEncoded: node)
+ }
+
+ guard let eContentRaw else {
+ return .init(eContentType: eContentType)
+ }
+ let eContent = try C(berEncoded: eContentRaw.bytes)
+ return .init(eContentType: eContentType, eContent: eContent, eContentRaw: eContentRaw)
+ }
+ }
+
+ @inlinable
+ public init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ try self.init(berEncoded: rootNode, withIdentifier: identifier)
+ }
+
+ @inlinable
+ public func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
+ try coder.appendConstructedNode(
+ identifier: identifier,
+ { coder in
+ try coder.serialize(eContentType)
+ if let eContent {
+ try coder.serialize(explicitlyTaggedWithTagNumber: 0, tagClass: .contextSpecific) { coder in
+ try coder.serialize(eContent)
+ }
+ }
+ }
+ )
+ }
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/InAppPurchase+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/InAppPurchase+SwiftASN1.swift
new file mode 100644
index 0000000..6c5b201
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/InAppPurchase+SwiftASN1.swift
@@ -0,0 +1,109 @@
+import Foundation
+import SwiftASN1
+
+extension InAppPurchase: BERImplicitlyTaggable {
+ public static let defaultIdentifier: SwiftASN1.ASN1Identifier = .set
+
+ public init(berEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws {
+ let attributes = try BER.set(
+ of: InAppReceiptPayloadAttribute.self,
+ identifier: identifier,
+ rootNode: berEncoded
+ )
+ try self.init(set: attributes)
+ }
+
+ public init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws {
+ try self.init(berEncoded: derEncoded, withIdentifier: identifier)
+ }
+
+ public func serialize(into _: inout DER.Serializer, withIdentifier _: SwiftASN1.ASN1Identifier) throws {}
+
+ public init(set: [InAppReceiptPayloadAttribute]) throws {
+ var originalTransactionIdentifier = ""
+ var productIdentifier = ""
+ var transactionIdentifier = ""
+ var purchaseDate: Date!
+ var originalPurchaseDate: Date?
+ var quantity = 1
+ var productType: InAppPurchase.`Type` = .unknown
+ var subscriptionExpirationDate: Date? = nil
+ var cancellationDate: Date? = nil
+ var webOrderLineItemID: Int? = nil
+ var subscriptionTrialPeriod = false
+ var subscriptionIntroductoryPricePeriod = false
+ var promotionalOfferIdentifier: String? = nil
+
+ for att in set {
+ guard let field = InAppPurchaseReceiptField(rawValue: att.type) else { continue }
+
+ switch field {
+ case .quantity:
+ let node = try DER.parse(att.value.bytes)
+ quantity = try Int(derEncoded: node)
+
+ case .productIdentifier:
+ productIdentifier = try String(att.value, as: .utf8)
+
+ case .productType:
+ let node = try DER.parse(att.value.bytes)
+ let typeValue = try Int32(derEncoded: node)
+ productType = Type(rawValue: typeValue) ?? .unknown
+
+ case .transactionIdentifier:
+ transactionIdentifier = (try? String(att.value, as: .utf8)) ?? ""
+
+ case .purchaseDate:
+ purchaseDate = try Date(att.value)
+
+ case .originalTransactionIdentifier:
+ originalTransactionIdentifier = try String(att.value, as: .utf8)
+
+ case .originalPurchaseDate:
+ originalPurchaseDate = try Date(att.value)
+
+ case .subscriptionExpirationDate:
+ subscriptionExpirationDate = try? Date(att.value)
+
+ case .cancellationDate:
+ cancellationDate = try? Date(att.value)
+
+ case .webOrderLineItemID:
+ let node = try DER.parse(att.value.bytes)
+ webOrderLineItemID = try Int(derEncoded: node)
+
+ case .subscriptionTrialPeriod:
+ let node = try DER.parse(att.value.bytes)
+ let value = try Int(derEncoded: node)
+ subscriptionTrialPeriod = value != 0
+
+ case .subscriptionIntroductoryPricePeriod:
+ let node = try DER.parse(att.value.bytes)
+ let value = try Int(derEncoded: node)
+ subscriptionIntroductoryPricePeriod = value != 0
+
+ case .promotionalOfferIdentifier:
+ promotionalOfferIdentifier = try? String(att.value, as: .utf8)
+
+ default:
+ continue
+ }
+ }
+
+ self.init(
+ productIdentifier: productIdentifier,
+ productType: productType,
+ transactionIdentifier: transactionIdentifier,
+ originalTransactionIdentifier: originalTransactionIdentifier,
+ purchaseDate: purchaseDate,
+ originalPurchaseDate: originalPurchaseDate,
+ subscriptionExpirationDate: subscriptionExpirationDate,
+ cancellationDate: cancellationDate,
+ subscriptionTrialPeriod: subscriptionTrialPeriod,
+ subscriptionIntroductoryPricePeriod: subscriptionIntroductoryPricePeriod,
+ webOrderLineItemID: webOrderLineItemID,
+ promotionalOfferIdentifier: promotionalOfferIdentifier,
+ quantity: quantity
+ )
+ }
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/InAppReceiptPayload+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/InAppReceiptPayload+SwiftASN1.swift
new file mode 100644
index 0000000..b685fc0
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/InAppReceiptPayload+SwiftASN1.swift
@@ -0,0 +1,181 @@
+import Foundation
+import SwiftASN1
+
+/// ReceiptAttribute ::= SEQUENCE {
+/// type INTEGER,
+/// version INTEGER,
+/// value OCTET STRING
+/// }
+public struct InAppReceiptPayloadAttribute: BERImplicitlyTaggable {
+ public static let defaultIdentifier: SwiftASN1.ASN1Identifier = .sequence
+
+ let type: Int
+ let version: Int
+ let value: ASN1OctetString
+
+ public init(type: Int, version: Int, value: ASN1OctetString) {
+ self.type = type
+ self.version = version
+ self.value = value
+ }
+
+ public init(berEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws {
+ self = try BER.sequence(berEncoded, identifier: identifier) { nodes in
+ let type = try Int(berEncoded: &nodes)
+ let version = try Int(berEncoded: &nodes)
+ let octetString = try ASN1OctetString(berEncoded: &nodes)
+
+ return .init(type: type, version: version, value: octetString)
+ }
+ }
+
+ public init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws {
+ try self.init(berEncoded: derEncoded, withIdentifier: identifier)
+ }
+
+ public func serialize(
+ into coder: inout SwiftASN1.DER.Serializer,
+ withIdentifier identifier: SwiftASN1.ASN1Identifier
+ ) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(type)
+ try coder.serialize(version)
+ try coder.serialize(value)
+ }
+ }
+}
+
+extension InAppReceiptPayload: BERImplicitlyTaggable {
+ public static var defaultIdentifier: SwiftASN1.ASN1Identifier {
+ .set
+ }
+
+ public init(berEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws {
+ let attributes = try BER.set(
+ of: InAppReceiptPayloadAttribute.self,
+ identifier: identifier,
+ rootNode: berEncoded
+ )
+ try self.init(set: attributes, rawBytes: berEncoded.encodedBytes)
+ }
+
+ public init(derEncoded: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws {
+ try self.init(berEncoded: derEncoded, withIdentifier: identifier)
+ }
+
+ public func serialize(into _: inout SwiftASN1.DER.Serializer, withIdentifier _: SwiftASN1.ASN1Identifier) throws {
+ fatalError("TPInAppReceipt doesn't support encoding")
+ }
+
+ public init(set: [InAppReceiptPayloadAttribute], rawBytes _: ArraySlice) throws {
+ var bundleIdentifier: String!
+ var bundleIdentifierData: Data!
+ var appVersion: String!
+ var originalAppVersion: String!
+ var originalPurchaseDate: Date?
+ var purchases: [InAppPurchase] = []
+ var opaqueValue: Data!
+ var receiptHash: Data!
+ var expirationDate: Date?
+ var receiptCreationDate: Date!
+ var ageRating: String!
+ var environment: String!
+ var appStoreID: Int?
+ var transactionDate: Date?
+ var fulfillmentToolVersion: Int?
+ var developerID: Int?
+ var downloadID: Int?
+ var installerVersionID: Int?
+
+ for att in set {
+ guard let field = AppReceiptField(rawValue: att.type) else { continue }
+
+ switch field {
+ case .bundleIdentifier:
+ bundleIdentifier = try String(att.value, as: .utf8)
+ bundleIdentifierData = Data(att.value.bytes)
+ case .appVersion:
+ appVersion = try String(att.value, as: .utf8)
+ case .opaqueValue:
+ opaqueValue = Data(att.value.bytes)
+ case .receiptHash:
+ receiptHash = Data(att.value.bytes)
+ case .inAppPurchaseReceipt:
+ try purchases.append(InAppPurchase(berEncoded: att.value.bytes))
+ case .originalAppVersion:
+ originalAppVersion = try String(att.value, as: .utf8)
+ case .originalAppPurchaseDate:
+ originalPurchaseDate = try Date(att.value)
+ case .expirationDate:
+ expirationDate = try Date(att.value)
+ case .receiptCreationDate:
+ receiptCreationDate = try Date(att.value)
+ case .ageRating:
+ ageRating = try String(att.value, as: .ascii)
+ case .environment:
+ environment = try String(att.value, as: .utf8)
+ case .appStoreID:
+ let node = try DER.parse(att.value.bytes)
+ appStoreID = try Int(derEncoded: node)
+ case .transactionDate:
+ transactionDate = try? Date(att.value)
+ case .fulfillmentToolVersion:
+ fulfillmentToolVersion = try Int(derEncoded: att.value.bytes)
+ case .developerID:
+ developerID = try Int(derEncoded: att.value.bytes)
+ case .downloadID:
+ downloadID = try Int(derEncoded: att.value.bytes)
+ case .installerVersionID:
+ installerVersionID = try Int(derEncoded: att.value.bytes)
+ default:
+ break
+ }
+ }
+
+ self.init(
+ bundleIdentifier: bundleIdentifier,
+ appVersion: appVersion,
+ originalAppVersion: originalAppVersion,
+ originalPurchaseDate: originalPurchaseDate,
+ purchases: purchases,
+ expirationDate: expirationDate,
+ bundleIdentifierData: bundleIdentifierData,
+ opaqueValue: opaqueValue,
+ receiptHash: receiptHash,
+ creationDate: receiptCreationDate,
+ ageRating: ageRating,
+ environment: Environment(rawValue: environment),
+ appStoreID: appStoreID,
+ transactionDate: transactionDate,
+ fulfillmentToolVersion: fulfillmentToolVersion,
+ developerID: developerID,
+ downloadID: downloadID,
+ installerVersionID: installerVersionID
+ )
+ }
+}
+
+extension String {
+ init(_ octetString: ASN1OctetString, as sourceEncoding: Encoding) throws {
+ switch sourceEncoding {
+ case Encoding.utf8:
+ self = try String(ASN1UTF8String(derEncoded: octetString.bytes))
+ case Encoding.ascii:
+ self = try String(ASN1IA5String(derEncoded: octetString.bytes))
+ default:
+ fatalError("Unhandled string source encoding: \(sourceEncoding.rawValue)")
+ }
+ }
+}
+
+extension Date {
+ init(_ octetString: ASN1OctetString) throws {
+ let str = try ASN1IA5String(berEncoded: octetString.bytes)
+
+ guard let date = Date.date(from: String(str)) else {
+ throw ASN1Error.invalidASN1Object(reason: "Invalid date format")
+ }
+
+ self = date
+ }
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/IssuerAndSerialNumber+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/IssuerAndSerialNumber+SwiftASN1.swift
new file mode 100644
index 0000000..97aadf2
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/IssuerAndSerialNumber+SwiftASN1.swift
@@ -0,0 +1,47 @@
+import SwiftASN1
+import X509
+
+/// ``IssuerAndSerialNumber`` is defined in ASN.1 as:
+/// ```
+/// IssuerAndSerialNumber ::= SEQUENCE {
+/// issuer Name,
+/// serialNumber CertificateSerialNumber }
+/// ```
+/// The definition of `Name` is taken from X.501 [X.501-88], and the
+/// definition of `CertificateSerialNumber` is taken from X.509 [X.509-97].
+///
+
+extension IssuerAndSerialNumber: DERImplicitlyTaggable {
+ @inlinable
+ public static var defaultIdentifier: ASN1Identifier {
+ .sequence
+ }
+
+ @inlinable
+ public init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ self = try DER.sequence(rootNode, identifier: identifier) { nodes in
+ let issuer = try DistinguishedName.derEncoded(&nodes)
+ let serialNumber = try ArraySlice(derEncoded: &nodes)
+ return .init(issuer: issuer, serialNumber: .init(bytes: serialNumber))
+ }
+ }
+
+ @inlinable
+ public func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(self.issuer)
+ try coder.serialize(self.serialNumber.bytes)
+ }
+ }
+}
+
+extension DistinguishedName {
+ @inlinable
+ static func derEncoded(_ sequenceNodeIterator: inout ASN1NodeCollection.Iterator) throws -> DistinguishedName {
+ // This is a workaround for the fact that, even though the conformance to DERImplicitlyTaggable is
+ // deprecated, Swift still prefers calling init(derEncoded:withIdentifier:) instead of this one.
+ let dnFactory: (inout ASN1NodeCollection.Iterator) throws -> DistinguishedName =
+ DistinguishedName.init(derEncoded:)
+ return try dnFactory(&sequenceNodeIterator)
+ }
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/SignedData+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/SignedData+SwiftASN1.swift
new file mode 100644
index 0000000..1ae704f
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/SignedData+SwiftASN1.swift
@@ -0,0 +1,103 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+import SwiftASN1
+import X509
+
+/// ``SignedData`` is defined in ASN.1 as:
+/// ```
+/// SignedData ::= SEQUENCE {
+/// version CMSVersion,
+/// digestAlgorithms DigestAlgorithmIdentifiers,
+/// encapContentInfo EncapsulatedContentInfo,
+/// certificates [0] IMPLICIT CertificateSet OPTIONAL,
+/// crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
+/// signerInfos SignerInfos }
+///
+/// DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
+/// DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+/// SignerInfos ::= SET OF SignerInfo
+/// CertificateSet ::= SET OF CertificateChoices
+///
+/// CertificateChoices ::= CHOICE {
+/// certificate Certificate,
+/// extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete
+/// v1AttrCert [1] IMPLICIT AttributeCertificateV1, -- Obsolete
+/// v2AttrCert [2] IMPLICIT AttributeCertificateV2,
+/// other [3] IMPLICIT OtherCertificateFormat }
+///
+/// OtherCertificateFormat ::= SEQUENCE {
+/// otherCertFormat OBJECT IDENTIFIER,
+/// otherCert ANY DEFINED BY otherCertFormat }
+/// ```
+/// - Note: At the moment we don't support `crls` (`RevocationInfoChoices`)
+///
+extension SignedData: DERParseable where C: BERImplicitlyTaggable {}
+extension SignedData: DERSerializable where C: BERImplicitlyTaggable {}
+extension SignedData: DERImplicitlyTaggable where C: BERImplicitlyTaggable {}
+extension SignedData: BERParseable where C: BERImplicitlyTaggable {}
+extension SignedData: BERSerializable where C: BERImplicitlyTaggable {}
+extension SignedData: BERImplicitlyTaggable where C: BERImplicitlyTaggable {
+ @inlinable
+ public static var defaultIdentifier: SwiftASN1.ASN1Identifier {
+ .sequence
+ }
+
+ @inlinable
+ public init(berEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ self = try BER.sequence(berEncoded, identifier: identifier) { nodes in
+ let version = try Version(rawValue: Int(berEncoded: &nodes))
+ let digestAlgorithms = try BER.set(of: AlgorithmIdentifier.self, identifier: .set, nodes: &nodes)
+
+ let encapContentInfo = try EncapsulatedContentInfo(berEncoded: &nodes)
+ var certificatesRaw: [Data] = []
+ let certificates = try BER.optionalImplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) {
+ node in
+ try BER.set(node, identifier: .init(tagWithNumber: 0, tagClass: .contextSpecific)) { iter in
+ var res: [Certificate] = []
+ while let next = iter.next() {
+ try res.append(Certificate(derEncoded: next))
+ certificatesRaw.append(Data(next.encodedBytes))
+ }
+ return res
+ }
+ }
+
+ // we need to skip this node even though we don't support it
+ _ = BER.optionalImplicitlyTagged(&nodes, tagNumber: 1, tagClass: .contextSpecific) { _ in }
+
+ let signerInfos = try BER.set(of: SignerInfo.self, identifier: .set, nodes: &nodes)
+
+ return .init(
+ version: version,
+ digestAlgorithms: digestAlgorithms,
+ encapContentInfo: encapContentInfo,
+ certificates: certificates ?? [],
+ certificatesRaw: certificatesRaw,
+ signerInfos: signerInfos
+ )
+ }
+ }
+
+ @inlinable
+ public init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ try self.init(berEncoded: derEncoded, withIdentifier: identifier)
+ }
+
+ @inlinable
+ public func serialize(into coder: inout DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws
+ {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(version.rawValue)
+ try coder.serializeSetOf(self.digestAlgorithms)
+ try coder.serialize(self.encapContentInfo)
+ if !certificates.isEmpty {
+ try coder.serializeSetOf(certificates, identifier: .init(tagWithNumber: 0, tagClass: .contextSpecific))
+ }
+ try coder.serializeSetOf(self.signerInfos, identifier: .set)
+ }
+ }
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/SignerIdentifier+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/SignerIdentifier+SwiftASN1.swift
new file mode 100644
index 0000000..b928fc6
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/SignerIdentifier+SwiftASN1.swift
@@ -0,0 +1,61 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCertificates open source project
+//
+// Copyright (c) 2023 Apple Inc. and the SwiftCertificates project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import SwiftASN1
+import X509
+
+/// ``SignerIdentifier`` is defined in ASN.1 as:
+/// ```
+/// SignerIdentifier ::= CHOICE {
+/// issuerAndSerialNumber IssuerAndSerialNumber,
+/// subjectKeyIdentifier [0] SubjectKeyIdentifier }
+/// ```
+///
+extension SignerIdentifier: DERParseable, BERParseable, DERSerializable, BERSerializable {
+ @usableFromInline
+ static let skiIdentifier = ASN1Identifier(tagWithNumber: 0, tagClass: .contextSpecific)
+
+ @inlinable
+ public init(derEncoded node: ASN1Node) throws {
+ switch node.identifier {
+ case IssuerAndSerialNumber.defaultIdentifier:
+ self = try .issuerAndSerialNumber(.init(derEncoded: node))
+
+ case Self.skiIdentifier:
+ self = try .subjectKeyIdentifier(
+ .init(
+ keyIdentifier: ASN1OctetString(
+ derEncoded: node,
+ withIdentifier: Self.skiIdentifier
+ ).bytes
+ )
+ )
+
+ default:
+ throw ASN1Error.unexpectedFieldType(node.identifier)
+ }
+ }
+
+ @inlinable
+ public func serialize(into coder: inout DER.Serializer) throws {
+ switch self {
+ case let .issuerAndSerialNumber(issuerAndSerialNumber):
+ try issuerAndSerialNumber.serialize(into: &coder)
+
+ case let .subjectKeyIdentifier(subjectKeyIdentifier):
+ try ASN1OctetString(contentBytes: subjectKeyIdentifier.keyIdentifier)
+ .serialize(into: &coder, withIdentifier: Self.skiIdentifier)
+ }
+ }
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/SignerInfo+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/SignerInfo+SwiftASN1.swift
new file mode 100644
index 0000000..5dc062b
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/SignerInfo+SwiftASN1.swift
@@ -0,0 +1,195 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCertificates open source project
+//
+// Copyright (c) 2023 Apple Inc. and the SwiftCertificates project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+import SwiftASN1
+
+/// ``SignerInfo`` is defined in ASN.1 as:
+/// ```
+/// SignerInfo ::= SEQUENCE {
+/// version CMSVersion,
+/// sid SignerIdentifier,
+/// digestAlgorithm DigestAlgorithmIdentifier,
+/// signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
+/// signatureAlgorithm SignatureAlgorithmIdentifier,
+/// signature SignatureValue,
+/// unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
+///
+/// SignatureValue ::= OCTET STRING
+/// DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+/// SignatureAlgorithmIdentifier ::= AlgorithmIdentifier
+/// ```
+/// - Note: If the `SignerIdentifier` is the CHOICE `issuerAndSerialNumber`,
+/// then the `version` MUST be 1. If the `SignerIdentifier` is `subjectKeyIdentifier`,
+/// then the `version` MUST be 3.
+///
+extension SignerInfo: DERImplicitlyTaggable, BERImplicitlyTaggable {
+ @inlinable
+ public static var defaultIdentifier: ASN1Identifier {
+ .sequence
+ }
+
+ @inlinable
+ public init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ self = try DER.sequence(rootNode, identifier: identifier) { nodes in
+ let version = try Version(rawValue: Int(derEncoded: &nodes))
+ let signerIdentifier = try SignerIdentifier(derEncoded: &nodes)
+ switch signerIdentifier {
+ case .issuerAndSerialNumber:
+ guard version == .v1 else {
+ throw Error.versionAndSignerIdentifierMismatch(
+ "expected \(Version.v1) but got \(version) where signerIdentifier is \(signerIdentifier)"
+ )
+ }
+ case .subjectKeyIdentifier:
+ guard version == .v3 else {
+ throw Error.versionAndSignerIdentifierMismatch(
+ "expected \(Version.v3) but got \(version) where signerIdentifier is \(signerIdentifier)"
+ )
+ }
+ }
+ let digestAlgorithm = try AlgorithmIdentifier(derEncoded: &nodes)
+
+ let signedAttrs = try DER.optionalImplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) {
+ node in
+ try DER.set(
+ of: Attribute.self,
+ identifier: .init(tagWithNumber: 0, tagClass: .contextSpecific),
+ rootNode: node
+ )
+ }
+
+ let signatureAlgorithm = try AlgorithmIdentifier(derEncoded: &nodes)
+ let signature = try ASN1OctetString(derEncoded: &nodes)
+
+ let unsignedAttrs = try DER.optionalImplicitlyTagged(&nodes, tagNumber: 1, tagClass: .contextSpecific) {
+ node in
+ try DER.set(
+ of: Attribute.self,
+ identifier: .init(tagWithNumber: 0, tagClass: .contextSpecific),
+ rootNode: node
+ )
+ }
+
+ return .init(
+ version: version,
+ signerIdentifier: signerIdentifier,
+ digestAlgorithm: digestAlgorithm,
+ signedAttrs: signedAttrs,
+ signatureAlgorithm: signatureAlgorithm,
+ signature: signature,
+ unsignedAttrs: unsignedAttrs
+ )
+ }
+ }
+
+ @inlinable
+ public init(berEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+ self = try BER.sequence(rootNode, identifier: identifier) { nodes in
+ let version = try Version(rawValue: Int(derEncoded: &nodes))
+ let signerIdentifier = try SignerIdentifier(berEncoded: &nodes)
+ switch signerIdentifier {
+ case .issuerAndSerialNumber:
+ guard version == .v1 else {
+ throw Error.versionAndSignerIdentifierMismatch(
+ "expected \(Version.v1) but got \(version) where signerIdentifier is \(signerIdentifier)"
+ )
+ }
+ case .subjectKeyIdentifier:
+ guard version == .v3 else {
+ throw Error.versionAndSignerIdentifierMismatch(
+ "expected \(Version.v3) but got \(version) where signerIdentifier is \(signerIdentifier)"
+ )
+ }
+ }
+ let digestAlgorithm = try AlgorithmIdentifier(berEncoded: &nodes)
+
+ // SignedAttrs MUST be in DER: https://datatracker.ietf.org/doc/html/rfc5652#section-2
+ let signedAttrs = try DER.optionalImplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) {
+ node in
+ try DER.set(
+ of: Attribute.self,
+ identifier: .init(tagWithNumber: 0, tagClass: .contextSpecific),
+ rootNode: node
+ )
+ }
+
+ let signatureAlgorithm = try AlgorithmIdentifier(berEncoded: &nodes)
+ let signature = try ASN1OctetString(berEncoded: &nodes)
+
+ let unsignedAttrs = try BER.optionalImplicitlyTagged(&nodes, tagNumber: 1, tagClass: .contextSpecific) {
+ node in
+ try BER.set(
+ of: Attribute.self,
+ identifier: .init(tagWithNumber: 0, tagClass: .contextSpecific),
+ rootNode: node
+ )
+ }
+
+ return .init(
+ version: version,
+ signerIdentifier: signerIdentifier,
+ digestAlgorithm: digestAlgorithm,
+ signedAttrs: signedAttrs,
+ signatureAlgorithm: signatureAlgorithm,
+ signature: signature,
+ unsignedAttrs: unsignedAttrs
+ )
+ }
+ }
+
+ @inlinable
+ public func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
+ try coder.appendConstructedNode(identifier: identifier) { coder in
+ try coder.serialize(self.version.rawValue)
+ try coder.serialize(self.signerIdentifier)
+ try coder.serialize(self.digestAlgorithm)
+ if let signedAttrs = self.signedAttrs {
+ try coder.serializeSetOf(signedAttrs, identifier: .init(tagWithNumber: 0, tagClass: .contextSpecific))
+ }
+ try coder.serialize(self.signatureAlgorithm)
+ try coder.serialize(self.signature)
+ if let unsignedAttrs = self.unsignedAttrs {
+ try coder.serializeSetOf(unsignedAttrs, identifier: .init(tagWithNumber: 1, tagClass: .contextSpecific))
+ }
+ }
+ }
+}
+
+// MARK: - Attribute Getters
+
+extension Array where Element == Attribute {
+ @inlinable
+ subscript(oid: ASN1ObjectIdentifier) -> Attribute? {
+ if let attr = first(where: { $0.attrType == oid }) {
+ return attr
+ }
+ return nil
+ }
+}
+
+extension ASN1ObjectIdentifier {
+ @usableFromInline
+ static let messageDigest: Self = [1, 2, 840, 113_549, 1, 9, 4]
+
+ @usableFromInline
+ static let signingTime: Self = [1, 2, 840, 113_549, 1, 9, 5]
+
+ @usableFromInline
+ static let contentType: Self = [1, 2, 840, 113_549, 1, 9, 3]
+}
diff --git a/Sources/Decoder/Engine/PCKS7+SwiftASN1/Time+SwiftASN1.swift b/Sources/Decoder/Engine/PCKS7+SwiftASN1/Time+SwiftASN1.swift
new file mode 100644
index 0000000..086ad20
--- /dev/null
+++ b/Sources/Decoder/Engine/PCKS7+SwiftASN1/Time+SwiftASN1.swift
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftCertificates open source project
+//
+// Copyright (c) 2022-2023 Apple Inc. and the SwiftCertificates project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+import SwiftASN1
+
+// Time ::= CHOICE {
+// utcTime UTCTime,
+// generalTime GeneralizedTime }
+extension Time: DERParseable, DERSerializable {
+ @inlinable
+ public init(derEncoded rootNode: ASN1Node) throws {
+ switch rootNode.identifier {
+ case GeneralizedTime.defaultIdentifier:
+ self = try .generalTime(GeneralizedTime(derEncoded: rootNode))
+ case UTCTime.defaultIdentifier:
+ self = try .utcTime(UTCTime(derEncoded: rootNode))
+ default:
+ throw ASN1Error.unexpectedFieldType(rootNode.identifier)
+ }
+ }
+
+ @inlinable
+ public func serialize(into coder: inout DER.Serializer) throws {
+ switch self {
+ case let .utcTime(utcTime):
+ try coder.serialize(utcTime)
+ case let .generalTime(generalizedTime):
+ try coder.serialize(generalizedTime)
+ }
+ }
+}
diff --git a/Sources/Decoder/Engine/SwiftASN1ReceiptDecoder.swift b/Sources/Decoder/Engine/SwiftASN1ReceiptDecoder.swift
new file mode 100644
index 0000000..83b1293
--- /dev/null
+++ b/Sources/Decoder/Engine/SwiftASN1ReceiptDecoder.swift
@@ -0,0 +1,31 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+import SwiftASN1
+
+public final class SwiftASN1ReceiptDecoder: AppReceiptDecoder.Engine {
+ public func decode(from data: Data) throws -> AppReceipt {
+ try AppReceipt(berEncoded: data)
+ }
+}
+
+extension BERParseable {
+ public init(berEncoded data: Data) throws {
+ try self.init(berEncoded: data.bytes)
+ }
+}
+
+extension DERParseable {
+ public init(derEncoded data: Data) throws {
+ try self.init(derEncoded: data.bytes)
+ }
+}
+
+extension Data {
+ fileprivate var bytes: [UInt8] {
+ Array(self)
+ }
+}
diff --git a/Sources/Decoding.swift b/Sources/Decoding.swift
new file mode 100644
index 0000000..f56cec0
--- /dev/null
+++ b/Sources/Decoding.swift
@@ -0,0 +1,52 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+extension AppReceiptDecoder {
+ /// The default receipt decoder using SwiftASN1.
+ ///
+ /// Creates a pre-configured decoder instance that uses the SwiftASN1 engine
+ /// for decoding App Store receipts.
+ public static var `default`: Self {
+ .init(engine: SwiftASN1ReceiptDecoder())
+ }
+}
+
+extension AppReceipt {
+ /// Creates and decodes an ``AppReceipt`` instance from raw receipt data.
+ ///
+ /// Decodes the provided receipt data using the default ASN.1 decoder to create
+ /// a fully parsed ``AppReceipt`` instance.
+ ///
+ /// - Parameter data: The raw receipt data (typically from the app bundle).
+ /// - Returns: A decoded ``AppReceipt`` instance.
+ /// - Throws: ``AppReceiptError/decodingFailed(_:)`` if the receipt data is invalid or cannot be decoded.
+ public static func receipt(from data: Data) throws(AppReceiptError) -> AppReceipt {
+ do {
+ return try AppReceiptDecoder.default.decode(from: data)
+ } catch let error as AppReceiptError {
+ throw error
+ } catch {
+ throw .decodingFailed(error)
+ }
+ }
+
+ /// Retrieves and decodes the local App Store receipt asynchronously.
+ ///
+ /// Loads the receipt bundled with the application from the App Store receipt URL and decodes it.
+ ///
+ /// - Returns: A decoded ``AppReceipt`` instance.
+ /// - Throws: ``AppReceiptError/appStoreReceiptNotFound`` if the receipt is not available,
+ /// or ``AppReceiptError/decodingFailed(_:)`` if the receipt cannot be decoded.
+ public static var local: AppReceipt {
+ get async throws(AppReceiptError) {
+ guard let data = await Bundle.main.appStoreReceiptData() else {
+ throw AppReceiptError.appStoreReceiptNotFound
+ }
+
+ return try receipt(from: data)
+ }
+ }
+}
diff --git a/Sources/Docs.docc/Architecture.md b/Sources/Docs.docc/Architecture.md
new file mode 100644
index 0000000..58418ec
--- /dev/null
+++ b/Sources/Docs.docc/Architecture.md
@@ -0,0 +1,133 @@
+# Architecture
+
+How the library is structured and where to plug in custom implementations.
+
+## Overview
+
+The library has three layers: **Core** (data models), **Decoder** (parsing), and **Validator** (verification). Both Decoder and Validator are pluggable.
+
+```
+┌──────────────────────────────────────────────┐
+│ Public API │
+│ AppReceipt extensions │
+├──────────────────────────────────────────────┤
+│ │
+│ ┌─────────────┐ ┌──────────────────────┐ │
+│ │ Decoder │ │ Validator │ │
+│ │ │ │ │ │
+│ │ Engine │ │ ReceiptValidator │ │
+│ │ protocol │ │ @VerifierBuilder │ │
+│ │ │ │ │ │
+│ │ SwiftASN1 │ │ X509ChainVerifier │ │
+│ │ (default) │ │ SignatureVerifier │ │
+│ │ │ │ HashVerifier │ │
+│ │ │ │ MetaVerifier │ │
+│ │ │ │ │ │
+│ └─────────────┘ └──────────────────────┘ │
+│ │
+├──────────────────────────────────────────────┤
+│ Core │
+│ │
+│ ContentInfo>│
+│ InAppPurchase, SignerInfo, etc. │
+│ │
+│ PKCS#7 types: ContentInfo, SignedData, │
+│ EncapsulatedContentInfo, SignerInfo, │
+│ AlgorithmIdentifier, Attribute, Version │
+└──────────────────────────────────────────────┘
+```
+
+## Core
+
+`AppReceipt` is a typealias:
+
+```swift
+public typealias AppReceipt = ContentInfo>
+```
+
+The generic PKCS#7 types (`ContentInfo`, `SignedData`, `EncapsulatedContentInfo`, `SignerInfo`, etc.) model the full ASN.1 structure. `InAppReceiptPayload` and `InAppPurchase` hold the decoded receipt data.
+
+All types are `Sendable` and `Hashable`.
+
+## Decoder
+
+```
+┌──────────────────┐
+│ AppReceiptDecoder│
+│ │
+│ engine: Engine │───▶ Protocol
+└──────────────────┘
+ │
+ ▼
+┌──────────────────────────┐
+│ SwiftASN1ReceiptDecoder │ (default)
+│ │
+│ Uses swift-asn1 BER/DER │
+│ parsing + PKCS7+SwiftASN1│
+│ extensions │
+└──────────────────────────┘
+```
+
+`AppReceiptDecoder` takes an `Engine` — a protocol with a single `decode(from:)` method. The default engine is `SwiftASN1ReceiptDecoder` which uses Apple's swift-asn1 for BER/DER parsing.
+
+To use a custom decoder:
+
+```swift
+struct MyEngine: AppReceiptDecoder.Engine {
+ func decode(from data: Data) throws -> AppReceipt {
+ // custom parsing
+ }
+}
+
+let decoder = AppReceiptDecoder(engine: MyEngine())
+let receipt = try decoder.decode(from: data)
+```
+
+## Validator
+
+```
+┌──────────────────────┐
+│ ReceiptValidator │
+│ │
+│ @VerifierBuilder │
+│ verifiers: [any │
+│ ReceiptVerifier] │
+└──────────┬───────────┘
+ │
+ │ TaskGroup (parallel)
+ │
+ ┌─────┼─────┬──────────┐
+ ▼ ▼ ▼ ▼
+ Chain Sig Hash Meta
+```
+
+`ReceiptValidator` composes `ReceiptVerifier` conformances via `@VerifierBuilder`. Default validation runs four verifiers in parallel using `TaskGroup`. If any fails, the rest are cancelled.
+
+### Implementing a Custom Verifier
+
+```swift
+struct MyVerifier: ReceiptVerifier {
+ func verify(_ receipt: ReceiptValidatable) async -> VerificationResult {
+ // custom check
+ return .valid
+ }
+}
+
+let validator = ReceiptValidator {
+ X509ChainVerifier(rootCertificates: [rootCert])
+ MyVerifier()
+}
+```
+
+### Verifier Variants
+
+Each verification concern has two implementations:
+
+| Concern | Async (default) | Blocking (Security.framework) |
+|---------|-----------------|-------------------------------|
+| Chain | `X509ChainVerifier` | `SecChainVerifier` |
+| Signature | `SignatureVerifier` | `SecSignatureVerifier` |
+| Hash | `HashVerifier` | `HashVerifier` (same) |
+| Meta | `MetaVerifier` | `MetaVerifier` (same) |
+
+The async variants use swift-certificates and swift-crypto. The `Sec` variants use Apple's Security.framework and are used in the blocking validation path.
diff --git a/Sources/Docs.docc/Blocking Mode.md b/Sources/Docs.docc/Blocking Mode.md
new file mode 100644
index 0000000..0cce9a1
--- /dev/null
+++ b/Sources/Docs.docc/Blocking Mode.md
@@ -0,0 +1,18 @@
+# Blocking Mode
+
+Synchronous API for contexts where async is not available.
+
+## Usage
+
+```swift
+@_spi(Blocking) import TPInAppReceipt
+
+let receipt = try AppReceipt.local_blocking
+let result = receipt.validate_blocking()
+```
+
+## How It Differs
+
+The blocking validator uses Security.framework verifiers (`SecChainVerifier`, `SecSignatureVerifier`) instead of the async swift-certificates/swift-crypto ones. Hash and metadata verification are the same.
+
+Blocking validation runs verifiers sequentially, not in parallel.
diff --git a/Sources/Docs.docc/Receipt Fields.md b/Sources/Docs.docc/Receipt Fields.md
new file mode 100644
index 0000000..421ad5c
--- /dev/null
+++ b/Sources/Docs.docc/Receipt Fields.md
@@ -0,0 +1,260 @@
+# Receipt Fields
+
+ASN.1 field types present in an App Store receipt.
+
+> Based on [Apple's Receipt Fields Reference](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html). Fields not documented by Apple are marked as reserved/unknown.
+
+## App Receipt Fields
+
+### Bundle Identifier
+
+The app's bundle identifier. Corresponds to the value of `CFBundleIdentifier` in the `Info.plist` file. Use this value to validate if the receipt was indeed generated for your app.
+
+- ASN.1 Field Type: 2
+- ASN.1 Field Value: UTF8STRING
+
+### App Version
+
+The app's version number. Corresponds to the value of `CFBundleVersion` (in iOS) or `CFBundleShortVersionString` (in macOS) in the `Info.plist` file.
+
+- ASN.1 Field Type: 3
+- ASN.1 Field Value: UTF8STRING
+
+### Opaque Value
+
+An opaque value used, with other data, to compute the SHA-1 hash during validation.
+
+- ASN.1 Field Type: 4
+- ASN.1 Field Value: A series of bytes
+
+### SHA-1 Hash
+
+A SHA-1 hash, used to validate the receipt.
+
+- ASN.1 Field Type: 5
+- ASN.1 Field Value: 20-byte SHA-1 digest
+
+### Environment
+
+The receipt's environment.
+
+- ASN.1 Field Type: 0
+- ASN.1 Field Value: UTF8STRING
+- Values: `Production`, `ProductionSandbox`, `Sandbox`, `Xcode`
+
+> Not documented by Apple in the original reference. Present in receipts from all environments.
+
+### App Store ID
+
+The App Store ID.
+
+- ASN.1 Field Type: 1
+- ASN.1 Field Value: INTEGER
+
+> Not documented by Apple. Only assigned in production.
+
+### Transaction Date
+
+The transaction date.
+
+- ASN.1 Field Type: 8
+- ASN.1 Field Value: IA5STRING, interpreted as an RFC 3339 date
+
+> Not documented by Apple.
+
+### Fulfillment Tool Version
+
+The fulfillment tool version.
+
+- ASN.1 Field Type: 9
+- ASN.1 Field Value: INTEGER
+
+> Not documented by Apple.
+
+### Age Rating
+
+The age rating of the app.
+
+- ASN.1 Field Type: 10
+- ASN.1 Field Value: UTF8STRING
+
+> Not documented by Apple.
+
+### Developer ID
+
+The developer ID.
+
+- ASN.1 Field Type: 11
+- ASN.1 Field Value: INTEGER
+
+> Not documented by Apple.
+
+### Receipt Creation Date
+
+The date when the app receipt was created. When validating the receipt, use this date to validate the receipt's signature.
+
+- ASN.1 Field Type: 12
+- ASN.1 Field Value: IA5STRING, interpreted as an RFC 3339 date
+
+### Download ID
+
+The download ID.
+
+- ASN.1 Field Type: 15
+- ASN.1 Field Value: INTEGER
+
+> Not documented by Apple.
+
+### Installer Version ID
+
+The installer version ID.
+
+- ASN.1 Field Type: 16
+- ASN.1 Field Value: INTEGER
+
+> Not documented by Apple.
+
+### In-App Purchase Receipt
+
+The receipt for an in-app purchase. In the ASN.1 file, there are multiple fields that all have type 17, each of which contains a single in-app purchase receipt.
+
+- ASN.1 Field Type: 17
+- ASN.1 Field Value: SET of in-app purchase receipt attributes
+
+An empty array is a valid receipt.
+
+### Original Purchase Date
+
+The date when the app was originally purchased.
+
+- ASN.1 Field Type: 18
+- ASN.1 Field Value: IA5STRING, interpreted as an RFC 3339 date
+
+### Original Application Version
+
+The version of the app that was originally purchased. Corresponds to the value of `CFBundleVersion` (in iOS) or `CFBundleShortVersionString` (in macOS). In the sandbox environment, the value is always `"1.0"`.
+
+- ASN.1 Field Type: 19
+- ASN.1 Field Value: UTF8STRING
+
+### Receipt Expiration Date
+
+The date that the app receipt expires. Only present for apps purchased through the Volume Purchase Program. If this key is not present, the receipt does not expire.
+
+- ASN.1 Field Type: 21
+- ASN.1 Field Value: IA5STRING, interpreted as an RFC 3339 date
+
+### Reserved Fields
+
+The following field type IDs are present in receipts but not documented by Apple:
+
+| ASN.1 Field Type | Status |
+|-------------------|--------|
+| 6 | Reserved |
+| 7 | Reserved |
+| 13 | Reserved |
+| 14 | Reserved |
+| 20 | Reserved |
+| 25 | Reserved |
+
+---
+
+## In-App Purchase Receipt Fields
+
+### Quantity
+
+The number of items purchased. Corresponds to the `quantity` property of the `SKPayment` object stored in the transaction's `payment` property.
+
+- ASN.1 Field Type: 1701
+- ASN.1 Field Value: INTEGER
+
+### Product Identifier
+
+The product identifier of the item that was purchased. Corresponds to the `productIdentifier` property of the `SKPayment` object stored in the transaction's `payment` property.
+
+- ASN.1 Field Type: 1702
+- ASN.1 Field Value: UTF8STRING
+
+### Transaction Identifier
+
+The transaction identifier of the item that was purchased. Corresponds to the transaction's `transactionIdentifier` property.
+
+- ASN.1 Field Type: 1703
+- ASN.1 Field Value: UTF8STRING
+
+### Purchase Date
+
+The date and time that the item was purchased. Corresponds to the transaction's `transactionDate` property. For auto-renewable subscriptions, this is either the purchase date or the renewal date.
+
+- ASN.1 Field Type: 1704
+- ASN.1 Field Value: IA5STRING, interpreted as an RFC 3339 date
+
+### Original Transaction Identifier
+
+For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier. All receipts in a chain of renewals for an auto-renewable subscription have the same value.
+
+- ASN.1 Field Type: 1705
+- ASN.1 Field Value: UTF8STRING
+
+### Original Purchase Date
+
+For a transaction that restores a previous transaction, the date of the original transaction. For auto-renewable subscriptions, this indicates the beginning of the subscription period, even if the subscription has been renewed.
+
+- ASN.1 Field Type: 1706
+- ASN.1 Field Value: IA5STRING, interpreted as an RFC 3339 date
+
+### Product Type
+
+The type of in-app product.
+
+- ASN.1 Field Type: 1707
+- ASN.1 Field Value: INTEGER
+- Values: 0 (non-consumable), 1 (consumable), 2 (non-renewing subscription), 3 (auto-renewable subscription)
+
+### Subscription Expiration Date
+
+The expiration date for the subscription. Only present for auto-renewable subscription receipts.
+
+- ASN.1 Field Type: 1708
+- ASN.1 Field Value: IA5STRING, interpreted as an RFC 3339 date
+
+### Web Order Line Item ID
+
+The primary key for identifying subscription purchases. This value is a unique ID that identifies purchase events across devices, including subscription renewal purchase events.
+
+- ASN.1 Field Type: 1711
+- ASN.1 Field Value: INTEGER
+
+### Cancellation Date
+
+For a transaction that was canceled, the time and date of the cancellation. For an auto-renewable subscription plan that was upgraded, the time and date of the upgrade transaction.
+
+- ASN.1 Field Type: 1712
+- ASN.1 Field Value: IA5STRING, interpreted as an RFC 3339 date
+
+Treat a canceled receipt the same as if no purchase had ever been made.
+
+### Subscription Trial Period
+
+Whether the subscription is in the free trial period.
+
+- ASN.1 Field Type: 1713
+- ASN.1 Field Value: INTEGER
+- Values: 1 (true), 0 (false)
+
+### Subscription Introductory Price Period
+
+Whether an auto-renewable subscription is in the introductory price period.
+
+- ASN.1 Field Type: 1719
+- ASN.1 Field Value: INTEGER
+- Values: 1 (true), 0 (false)
+
+If a previous subscription period in the receipt has a value of `true` for either the trial period or introductory price period, the user is not eligible for a free trial or introductory price within that subscription group.
+
+### Promotional Offer Identifier
+
+The identifier of the subscription offer redeemed by the user.
+
+- ASN.1 Field Type: 1721
+- ASN.1 Field Value: UTF8STRING
diff --git a/Sources/Docs.docc/TPInAppReceipt 4.0 Migration Guide.md b/Sources/Docs.docc/TPInAppReceipt 4.0 Migration Guide.md
new file mode 100644
index 0000000..f11f2e8
--- /dev/null
+++ b/Sources/Docs.docc/TPInAppReceipt 4.0 Migration Guide.md
@@ -0,0 +1,120 @@
+# Migrating from v3 to v4
+
+What changed and how to update your code.
+
+## Dependencies
+
+```swift
+.package(url: "https://github.com/tikhop/TPInAppReceipt.git", from: "4.0.0")
+```
+
+## Platform Requirements
+
+- iOS 12 → iOS 13
+- macOS 10.13 → macOS 10.15
+- tvOS 12 → tvOS 13
+- Swift 5.9 → Swift 6.0
+
+## Type Changes
+
+```swift
+// Before
+let receipt = try InAppReceipt.localReceipt()
+// or
+let receipt = try InAppReceipt()
+
+// After
+let receipt = try await AppReceipt.local
+```
+
+```swift
+// Before
+let receipt = try InAppReceipt.receipt(from: data)
+
+// After
+let receipt = try AppReceipt.receipt(from: data)
+```
+
+## Validation
+
+```swift
+// Before
+do {
+ try receipt.verify()
+} catch IARError.validationFailed(reason: .hashValidation) {
+ // ...
+}
+
+// After
+let result = await receipt.validate()
+switch result {
+case .valid:
+ break
+case .invalid(let error):
+ // error is typed: ReceiptValidatorError, ChainVerificationError, etc.
+ print(error)
+}
+```
+
+Individual verification methods (`verifyHash()`, `verifySignature()`, `verifyBundleIdentifier()`) are removed. Use custom `ReceiptValidator` composition instead. See .
+
+## Error Types
+
+| v3 | v4 |
+|----|-----|
+| `IARError` | `AppReceiptError` |
+| `IARError.validationFailed(reason:)` | `ReceiptValidatorError`, `ChainVerificationError`, `HashVerificationError`, etc. |
+
+## Property Changes
+
+- `originalAppVersion` is now `String?` (was `String`)
+- `ageRating` is now `String?` (was `String`)
+- `environment` is now `InAppReceiptPayload.Environment` enum (was `String`). Update comparisons: `receipt.environment == .production` instead of `receipt.environment == "Production"`.
+- `signature` is now `Data` (was `Data?`)
+- `InAppPurchase` properties are now `let` (was `var`)
+- `InAppPurchase.originalPurchaseDate` is now `Date?` (was `Date!`)
+- `autoRenewablePurchases` — same API, works on `AppReceipt`
+- `activeAutoRenewableSubscriptionPurchases` — same API
+
+## Blocking API
+
+If you prefer synchronous api, import the library with `@_spi(Blocking)` to access blocking variants of the async API.
+
+```swift
+@_spi(Blocking) import TPInAppReceipt
+
+// Load receipt
+let receipt = try AppReceipt.local_blocking
+
+// Validate
+let result = receipt.validate_blocking()
+```
+
+See for more details.
+
+## Receipt Refresh
+
+```swift
+// Before
+InAppReceipt.refresh { error in
+ // ...
+}
+
+// After
+try await AppReceipt.refresh()
+```
+
+`cancelRefreshSession()` and `isReceiptRefreshingNow` are removed. Use Swift Concurrency task cancellation instead.
+
+## Receipt Fields
+
+`InAppReceiptField` has been renamed to `AppReceiptField` and changed from a struct with static constants to an enum. Purchase-specific fields are now in a separate `InAppPurchaseReceiptField` enum.
+
+## Removed
+
+- Objective-C bindings
+- CocoaPods support
+- `isValid` computed property — use `await receipt.validate()` instead
+- `base64` computed property
+- `SKSubscriptionGroup` class and related `SKProductsResponse` extensions
+- `isEligibleForIntroductoryOffer(for: SKSubscriptionGroup)` overload — use `Set` variant instead
diff --git a/Sources/Docs.docc/Validating Receipt.md b/Sources/Docs.docc/Validating Receipt.md
new file mode 100644
index 0000000..1104fbf
--- /dev/null
+++ b/Sources/Docs.docc/Validating Receipt.md
@@ -0,0 +1,63 @@
+# Validating a Receipt
+
+Verify the authenticity and integrity of an App Store receipt.
+
+## Default Validation
+
+Runs four checks in parallel: certificate chain, signature, hash, and metadata.
+
+```swift
+let result = await receipt.validate()
+
+switch result {
+case .valid:
+ break
+case .invalid(let error):
+ print(error)
+}
+```
+
+The validator auto-selects the Apple root certificate based on the receipt's environment.
+
+## Custom Validators
+
+Build your own with `@VerifierBuilder`:
+
+```swift
+let validator = ReceiptValidator {
+ X509ChainVerifier(rootCertificates: [rootCert])
+ SignatureVerifier()
+ HashVerifier(deviceIdentifier: deviceId)
+ MetaVerifier(
+ appVersionProvider: { Bundle.main.appVersion },
+ bundleIdentifierProvider: { Bundle.main.bundleIdentifier }
+ )
+}
+
+let result = await validator.validate(receipt)
+```
+
+Include or exclude any verifier. They run in parallel via `TaskGroup` — if any fails, the rest are cancelled.
+
+## Built-in Verifiers
+
+| Verifier | What it checks |
+|----------|---------------|
+| `X509ChainVerifier` | Certificate chain against Apple root (swift-certificates) |
+| `SecChainVerifier` | Certificate chain via Security.framework |
+| `SignatureVerifier` | PKCS#7 signature (swift-crypto) |
+| `SecSignatureVerifier` | PKCS#7 signature via Security.framework |
+| `HashVerifier` | SHA-1 hash: device ID + opaque value + bundle ID |
+| `MetaVerifier` | Bundle identifier and app version match |
+
+`X509`/`Signature` variants are async. `Sec` variants use Security.framework and work in blocking contexts.
+
+## Error Types
+
+- ``ReceiptValidatorError`` — invalid structure, missing root certificate, missing device identifier
+- ``ChainVerificationError`` — certificate data invalid, chain validation failed, revocation check failed
+- ``SignatureVerificationError`` — invalid key, invalid signature
+- ``HashVerificationError`` — missing device identifier, hash mismatch
+- ``MetaVerificationError`` — bundle identifier mismatch, version mismatch
+
+See for how the validation layer fits into the overall design.
diff --git a/Sources/Docs.docc/Working with Receipt.md b/Sources/Docs.docc/Working with Receipt.md
new file mode 100644
index 0000000..5149b8c
--- /dev/null
+++ b/Sources/Docs.docc/Working with Receipt.md
@@ -0,0 +1,73 @@
+# Working with a Receipt
+
+Decode and read App Store receipt data.
+
+## Decoding
+
+```swift
+import TPInAppReceipt
+
+// Local receipt
+let receipt = try await AppReceipt.local
+
+// From raw data
+let receipt = try AppReceipt.receipt(from: data)
+```
+
+`AppReceipt` is a typealias over `ContentInfo>` — the full PKCS#7 structure is accessible.
+
+## Reading
+
+```swift
+receipt.bundleIdentifier
+receipt.appVersion
+receipt.originalAppVersion // String?
+receipt.environment // .production, .sandbox, .xcode, .productionSandbox
+receipt.creationDate
+receipt.expirationDate // VPP only
+```
+
+See for the full list of fields.
+
+## Purchases
+
+```swift
+receipt.purchases // [InAppPurchase]
+receipt.hasPurchases
+receipt.autoRenewablePurchases
+receipt.activeAutoRenewableSubscriptionPurchases
+```
+
+Query by product:
+
+```swift
+receipt.purchases(ofProductIdentifier: "com.example.sub")
+receipt.containsPurchase(ofProductIdentifier: "com.example.premium")
+
+receipt.hasActiveAutoRenewableSubscription(
+ ofProductIdentifier: "com.example.sub",
+ forDate: Date()
+)
+
+receipt.lastAutoRenewableSubscriptionPurchase(
+ ofProductIdentifier: "com.example.sub"
+)
+```
+
+## Introductory Offer Eligibility
+
+Pass product identifiers within the same subscription group:
+
+```swift
+receipt.isEligibleForIntroductoryOffer(
+ for: ["com.example.bronze", "com.example.silver", "com.example.gold"]
+)
+```
+
+## Refreshing
+
+Request an updated receipt from the App Store:
+
+```swift
+try await AppReceipt.refresh()
+```
diff --git a/Sources/Error.swift b/Sources/Error.swift
new file mode 100644
index 0000000..126cb06
--- /dev/null
+++ b/Sources/Error.swift
@@ -0,0 +1,24 @@
+/// Errors that can occur when working with app receipts.
+public enum AppReceiptError: Error, Sendable {
+ /// The app store receipt was not found on the device.
+ ///
+ /// This typically occurs when trying to access the local receipt for an app
+ /// that hasn't been purchased from the App Store.
+ case appStoreReceiptNotFound
+
+ /// The receipt content is invalid or corrupted.
+ ///
+ /// - Parameter error: The underlying error that caused the content to be invalid.
+ case receiptContentInvalid(any Error)
+
+ /// The receipt payload is missing or invalid.
+ ///
+ /// This indicates the receipt structure doesn't contain the expected payload data.
+ case receiptPayloadMissingOrInvalid
+
+ /// Receipt decoding failed.
+ ///
+ /// This occurs when the receipt data cannot be decoded from its ASN.1 format.
+ /// - Parameter error: The underlying error that caused the decoding failure.
+ case decodingFailed(any Error)
+}
diff --git a/Sources/Extensions/Bundle+Extension.swift b/Sources/Extensions/Bundle+Extension.swift
new file mode 100644
index 0000000..1dd1327
--- /dev/null
+++ b/Sources/Extensions/Bundle+Extension.swift
@@ -0,0 +1,50 @@
+import Foundation
+
+extension Bundle {
+ /// Appropriate app version for receipt validation
+ var appVersion: String? {
+ #if targetEnvironment(macCatalyst) || os(macOS)
+ let dictKey = "CFBundleShortVersionString"
+ #else
+ let dictKey = "CFBundleVersion"
+ #endif
+
+ return infoDictionary?[dictKey] as? String
+ }
+
+ /// Retrieve local App Store Receipt Data
+ ///
+ /// - Returns: 'Data' object that represents local receipt
+ /// - throws: An error if receipt file not found or 'Data' can't be created
+ func appStoreReceiptData() async -> Data? {
+ guard let receiptUrl = appStoreReceiptURL else { return nil }
+ return try? await Data.readBytes(from: receiptUrl)
+ }
+
+ /// Retrieve local App Store Receipt Data in base64 string
+ ///
+ /// - Returns: 'Data' object that represents local receipt
+ /// - throws: An error if receipt file not found or 'Data' can't be created
+ public func appStoreReceiptBase64() async -> String? {
+ await appStoreReceiptData()?.base64EncodedString()
+ }
+
+ static func appleRootCertificateData(testing: Bool) async -> Data? {
+ let filename = testing ? "StoreKitTestCertificate.cer" : "AppleIncRootCertificate.cer"
+ guard let url = url(for: filename) else {
+ return nil
+ }
+
+ return try? await Data.readBytes(from: url)
+ }
+
+ static func url(for file: String) -> URL? {
+ for bundle in [Bundle.module, Bundle.main] {
+ if let url = bundle.url(forResource: file, withExtension: "") {
+ return url
+ }
+ }
+
+ return nil
+ }
+}
diff --git a/Sources/Extensions/Data+Extension.swift b/Sources/Extensions/Data+Extension.swift
new file mode 100644
index 0000000..5aec3d6
--- /dev/null
+++ b/Sources/Extensions/Data+Extension.swift
@@ -0,0 +1,11 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+extension Data {
+ static func readBytes(from url: URL) async throws -> Data {
+ try await URLSession.shared.data(from: url).0
+ }
+}
diff --git a/Sources/Extensions/Date+Extension.swift b/Sources/Extensions/Date+Extension.swift
new file mode 100644
index 0000000..ea53374
--- /dev/null
+++ b/Sources/Extensions/Date+Extension.swift
@@ -0,0 +1,18 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+extension Date {
+ static func date(from rfc3339String: String) -> Date? {
+ rfc3339DateFormatter.date(from: rfc3339String)
+ }
+}
+
+private nonisolated(unsafe) let rfc3339DateFormatter: ISO8601DateFormatter = {
+ let formatter = ISO8601DateFormatter()
+ formatter.formatOptions = .withInternetDateTime
+ formatter.timeZone = TimeZone(abbreviation: "UTC")
+ return formatter
+}()
diff --git a/Sources/Extensions/UUID+Extension.swift b/Sources/Extensions/UUID+Extension.swift
new file mode 100644
index 0000000..f1c2487
--- /dev/null
+++ b/Sources/Extensions/UUID+Extension.swift
@@ -0,0 +1,13 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+extension UUID {
+ @inlinable
+ var data: Data {
+ var uuidBytes = uuid
+ return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes))
+ }
+}
diff --git a/Sources/Extras.swift b/Sources/Extras.swift
index d68e61a..e1aa2fc 100644
--- a/Sources/Extras.swift
+++ b/Sources/Extras.swift
@@ -1,262 +1,289 @@
-//
-// Extras.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 13.02.2020.
-// Copyright © 2021 Pavel Tikhonenko. All rights reserved.
-//
+extension AppReceipt {
+ /// Retrieves the original transaction identifier for a product.
+ ///
+ /// Gets the original transaction ID of the first purchase for the specified product identifier.
+ ///
+ /// - Parameter productIdentifier: The product identifier to search for.
+ /// - Returns: The original transaction identifier, or nil if no purchase exists for the product.
+ public func originalTransactionIdentifier(ofProductIdentifier productIdentifier: String) -> String? {
+ purchases(ofProductIdentifier: productIdentifier)
+ .first?
+ .originalTransactionIdentifier
+ }
-#if canImport(StoreKit)
+ /// Checks whether the receipt contains a purchase for a specific product.
+ ///
+ /// Determines if the receipt includes any purchase record for the specified product identifier.
+ ///
+ /// - Parameter productIdentifier: The product identifier to search for.
+ /// - Returns: `true` if a purchase exists for the product, `false` otherwise.
+ public func containsPurchase(ofProductIdentifier productIdentifier: String) -> Bool {
+ for item in purchases {
+ if item.productIdentifier == productIdentifier {
+ return true
+ }
+ }
-import Foundation
-import StoreKit
-
-@available(watchOSApplicationExtension 6.2, *)
-fileprivate var refreshSession: RefreshSession?
-
-@available(tvOS 12.0, *)
-@available(macOS 10.14, *)
-@available(iOS 12.0, *)
-public class SKSubscriptionGroup
-{
- let identifier: GroupIdentifier
- var products: Set = []
-
- init(with identifier: GroupIdentifier)
- {
- self.identifier = identifier
- self.products = []
- }
-
- init(with products: Set)
- {
- guard let gid = products.first?.subscriptionGroupIdentifier else
- {
- fatalError("All products must have subscriptionGroupIdentifier")
- }
-
- self.identifier = gid
- self.products = products
- }
-
- func insert(product: SKProduct)
- {
- if identifier != product.subscriptionGroupIdentifier
- {
- fatalError("`Product.subscriptionGroupIdentifier` must be equal to current identifier")
- }
-
- products.insert(product)
- }
-
- func contains(_ productIdentifier: String) -> Bool
- {
- return products.contains(where: { $0.productIdentifier == productIdentifier })
- }
+ return false
+ }
+
+ /// Retrieves all purchases for a specific product identifier.
+ ///
+ /// Finds all ``InAppPurchase`` records matching the specified product identifier.
+ /// By default, results are sorted by purchase date in descending order (most recent first).
+ ///
+ /// - Parameters:
+ /// - productIdentifier: The product identifier to search for.
+ /// - sort: An optional custom sorting function. If not provided, purchases are sorted by date descending.
+ /// - Returns: An array of ``InAppPurchase`` objects for the product, empty if none exist.
+ public func purchases(
+ ofProductIdentifier productIdentifier: String,
+ sortedBy sort: ((InAppPurchase, InAppPurchase) -> Bool)? = nil
+ ) -> [InAppPurchase] {
+ let filtered: [InAppPurchase] = purchases.filter {
+ $0.productIdentifier == productIdentifier
+ }
+
+ guard let sort = sort else {
+ return filtered.sorted { $0.purchaseDate > $1.purchaseDate }
+ }
+ return filtered.sorted { sort($0, $1) }
+ }
+
+ /// Retrieves an active auto-renewable subscription purchase for a product at a specific date.
+ ///
+ /// Finds the first active ``InAppPurchase`` for the specified product identifier
+ /// that is active on the given date.
+ ///
+ /// - Parameters:
+ /// - productIdentifier: The product identifier to search for.
+ /// - date: The date to check subscription activity against.
+ /// - Returns: An active ``InAppPurchase`` if one exists, nil otherwise.
+ public func activeAutoRenewableSubscriptionPurchases(
+ ofProductIdentifier productIdentifier: String,
+ forDate date: Date
+ ) -> InAppPurchase? {
+ let filtered = purchases(ofProductIdentifier: productIdentifier)
+
+ for purchase in filtered {
+ if purchase.isActiveAutoRenewableSubscription(forDate: date) {
+ return purchase
+ }
+ }
+
+ return nil
+ }
+
+ /// Retrieves the most recent auto-renewable subscription purchase for a product.
+ ///
+ /// Finds the ``InAppPurchase`` for the specified product identifier with the latest
+ /// subscription expiration date.
+ ///
+ /// - Parameter productIdentifier: The product identifier to search for.
+ /// - Returns: The most recent ``InAppPurchase`` if one exists, nil otherwise.
+ public func lastAutoRenewableSubscriptionPurchase(
+ ofProductIdentifier productIdentifier: String
+ ) -> InAppPurchase? {
+ var purchase: InAppPurchase? = nil
+ let filtered = purchases(ofProductIdentifier: productIdentifier)
+
+ var lastInterval: TimeInterval = 0
+ for iap in filtered {
+ if let thisInterval = iap.subscriptionExpirationDate?.timeIntervalSince1970 {
+ if purchase == nil || thisInterval > lastInterval {
+ purchase = iap
+ lastInterval = thisInterval
+ }
+ }
+ }
+
+ return purchase
+ }
+
+ /// Checks whether there is an active subscription for a product at a specific date.
+ ///
+ /// Determines if the receipt contains an active ``InAppPurchase`` for the specified product identifier
+ /// that is active on the given date.
+ ///
+ /// - Parameters:
+ /// - productIdentifier: The product identifier to search for.
+ /// - date: The date to check subscription activity against.
+ /// - Returns: `true` if an active subscription exists, `false` otherwise.
+ public func hasActiveAutoRenewableSubscription(
+ ofProductIdentifier productIdentifier: String,
+ forDate date: Date
+ ) -> Bool {
+ activeAutoRenewableSubscriptionPurchases(
+ ofProductIdentifier: productIdentifier,
+ forDate: date
+ ) != nil
+ }
+
+ /// Returns whether the customer is eligible for an introductory offer
+ /// within the provided subscription group.
+ ///
+ /// Returns `true` if the customer has never redeemed an introductory offer
+ /// or free trial for any product in the subscription group.
+ ///
+ /// - Parameter group: A set of product identifiers in the subscription group.
+ /// - Returns: `true` if the customer is eligible for an introductory offer
+ /// on any auto-renewable subscription within the subscription group; `false` otherwise.
+ public func isEligibleForIntroOffer(for group: Set) -> Bool {
+ payload.isEligibleForIntroOffer(for: group)
+ }
+
+ /// Returns whether the customer is eligible for an introductory offer
+ /// for the specified product.
+ ///
+ /// Returns `true` if the customer has never redeemed an introductory offer
+ /// or free trial for this product.
+ ///
+ /// - Parameter productIdentifier: The product identifier to check eligibility for.
+ /// - Returns: `true` if the customer is eligible for an introductory offer; `false` otherwise.
+ public func isEligibleForIntroOffer(for productIdentifier: String) -> Bool {
+ payload.isEligibleForIntroOffer(for: productIdentifier)
+ }
}
-public typealias GroupIdentifier = String
-
-@available(macOS 10.14, *)
-public extension SKProductsResponse
-{
- /// Build a `SKSubscriptionGroup` object
- ///
- /// We assume that all retrieved products `(SKProduct)` belong to the same subscription group
- ///
- /// - Returns `SKSubscriptionGroup`. Empty if no subscription groups found
- var subscriptionGroup: SKSubscriptionGroup
- {
- var group: SKSubscriptionGroup!
-
- for p in products
- {
- guard let pgid = p.subscriptionGroupIdentifier else
- {
- continue
- }
-
- if group == nil
- {
- group = SKSubscriptionGroup(with: pgid)
- }
-
- group.insert(product: p)
- }
-
- guard let g = group else
- {
- fatalError("`group` can't be nil.")
- }
-
- return g
- }
-
- /// Build a dictionary that contains the subscription groups
- ///
- /// The dictionary contains `SKSubscriptionGroup` objects where the key is a group identifier `GroupIdentifier`
- ///
- /// - Returns `[GroupIdentifier: SKSubscriptionGroup]`. Empty if no subscription groups found
- var subscriptionGroups: [GroupIdentifier: SKSubscriptionGroup]
- {
- var groups: [GroupIdentifier: SKSubscriptionGroup] = [:]
-
- for p in products
- {
- guard let gid = p.subscriptionGroupIdentifier else
- {
- continue
- }
-
- guard let group = groups[gid] else
- {
- let g = SKSubscriptionGroup(with: gid)
- g.insert(product: p)
-
- groups[gid] = g
-
- continue
- }
-
- group.insert(product: p)
- }
-
- return groups
- }
+// MARK: - InAppPurchase Refund Status
+
+extension InAppPurchase {
+ /// A Boolean value indicating whether the purchase has been canceled or refunded.
+ ///
+ /// Applies to auto-renewable subscriptions, non-consumable products, and non-renewing subscriptions.
+ /// Returns `true` when Apple customer support has canceled the transaction or the customer received a refund.
+ ///
+ /// - Note: For auto-renewable subscriptions, this also returns `true` when the subscription
+ /// was upgraded to a different product. The local receipt does not include a cancellation reason
+ /// to distinguish refunds from upgrades.
+ public var isRefunded: Bool {
+ cancellationDate != nil
+ }
}
-@available(macOS 10.14, *)
-public extension InAppReceipt
-{
-
- /// Refresh local in-app receipt
- ///
- /// - Parameter completion: handler for result
- @available(watchOSApplicationExtension 6.2, *)
- static func refresh(completion: @escaping IAPRefreshRequestResult)
- {
- if refreshSession != nil
- {
- completion(IARError.receiptRefreshingInProgress)
- return
- }
-
- refreshSession = RefreshSession()
- refreshSession!.refresh { (error) in
- completion(error)
- InAppReceipt.destroyRefreshSession()
+// MARK: - InAppReceiptPayload Intro Offer Eligibility
+
+extension InAppReceiptPayload {
+ /// Returns whether the customer is eligible for an introductory offer
+ /// within the provided subscription group.
+ ///
+ /// Returns `true` if the customer has never redeemed an introductory offer
+ /// or free trial for any product in the subscription group.
+ ///
+ /// - Parameter group: A set of product identifiers in the subscription group.
+ /// - Returns: `true` if the customer is eligible for an introductory offer
+ /// on any auto-renewable subscription within the subscription group; `false` otherwise.
+ public func isEligibleForIntroOffer(for group: Set) -> Bool {
+ !purchases.contains {
+ group.contains($0.productIdentifier)
+ && ($0.subscriptionTrialPeriod || $0.subscriptionIntroductoryPricePeriod)
}
}
- /// Cancel refreshing local in-app receipt
- @available(watchOSApplicationExtension 6.2, *)
- static func cancelRefreshSession()
- {
- refreshSession?.cancel()
- destroyRefreshSession()
- }
-
- @available(watchOSApplicationExtension 6.2, *)
- static fileprivate func destroyRefreshSession()
- {
- refreshSession = nil
+ /// Returns whether the customer is eligible for an introductory offer
+ /// for the specified product.
+ ///
+ /// Returns `true` if the customer has never redeemed an introductory offer
+ /// or free trial for this product.
+ ///
+ /// - Parameter productIdentifier: The product identifier to check eligibility for.
+ /// - Returns: `true` if the customer is eligible for an introductory offer; `false` otherwise.
+ public func isEligibleForIntroOffer(for productIdentifier: String) -> Bool {
+ !purchases.contains {
+ $0.productIdentifier == productIdentifier
+ && ($0.subscriptionTrialPeriod || $0.subscriptionIntroductoryPricePeriod)
+ }
}
-
- /// Check whether receipt is refreshing now
- ///
- /// - Returns `true` if receipt is refreshing now, otherwise `false`
- @available(watchOSApplicationExtension 6.2, *)
- static var isReceiptRefreshingNow: Bool
- {
- refreshSession != nil
- }
-
- /// Check whether user is eligible for introductory offer for any products within the same subscription group
- ///
- /// - Returns `false` if user isn't eligible for introductory offer, otherwise `true`
- func isEligibleForIntroductoryOffer(for group: SKSubscriptionGroup) -> Bool
- {
- let array = purchases.filter { $0.subscriptionTrialPeriod || $0.subscriptionIntroductoryPricePeriod }
- .filter { group.contains($0.productIdentifier) }
-
- return array.isEmpty
- }
}
-public typealias IAPRefreshRequestResult = ((Error?) -> ())
+#if canImport(StoreKit)
-@available(watchOSApplicationExtension 6.2, *)
-fileprivate class RefreshSession : NSObject, SKRequestDelegate
-{
- private let receiptRefreshRequest = SKReceiptRefreshRequest()
- private var completion: IAPRefreshRequestResult?
-
- override init()
- {
- super.init()
+import Foundation
+@preconcurrency import StoreKit
- receiptRefreshRequest.delegate = self
+extension AppReceipt {
+ /// Requests a refresh of the app receipt from the App Store.
+ ///
+ /// Initiates an asynchronous receipt refresh request using StoreKit.
+ /// This is useful when you need to update the local receipt with new purchase information.
+ ///
+ /// If a refresh is already in progress, subsequent calls will await the existing
+ /// request rather than starting a new one.
+ ///
+ /// - Throws: An error if the receipt refresh request fails.
+ public static func refresh() async throws {
+ try await RefreshCoordinator.shared.refresh()
}
-
- func refresh(completion: @escaping IAPRefreshRequestResult)
- {
- self.completion = completion
-
- receiptRefreshRequest.start()
+}
+
+private final class RefreshCoordinator: @unchecked Sendable {
+ static let shared = RefreshCoordinator()
+
+ private let lock = NSLock()
+ private var ongoingTask: Task?
+
+ func refresh() async throws {
+ let task: Task = lock.withLock {
+ if let existing = ongoingTask {
+ return existing
+ }
+ let task = Task {
+ defer {
+ self.lock.withLock { self.ongoingTask = nil }
+ }
+ try await Self.performRefresh()
+ }
+ ongoingTask = task
+ return task
+ }
+ try await task.value
}
-
- func requestDidFinish(_ request: SKRequest)
- {
- requestDidFinish(with: nil)
- receiptRefreshRequest.cancel()
+
+ private static func performRefresh() async throws {
+ let request = SKReceiptRefreshRequest()
+ let delegate = RefreshDelegate()
+
+ request.delegate = delegate
+
+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in
+ delegate.setContinuation(continuation)
+ request.start()
+ }
}
-
- func request(_ request: SKRequest, didFailWithError error: Error)
- {
- print("Something went wrong: \(error.localizedDescription)")
-
- requestDidFinish(with: error)
+}
+
+private final class RefreshDelegate: NSObject, SKRequestDelegate, @unchecked Sendable {
+ private let lock = NSLock()
+ private var continuation: CheckedContinuation?
+
+ func setContinuation(_ continuation: CheckedContinuation) {
+ lock.withLock {
+ self.continuation = continuation
+ }
}
-
- func requestDidFinish(with error: Error?)
- {
- DispatchQueue.main.async { [weak self] in
- self?.completion?(error)
+
+ func requestDidFinish(_: SKRequest) {
+ resume(with: .success(()))
+ }
+
+ func request(_: SKRequest, didFailWithError error: Error) {
+ resume(with: .failure(error))
+ }
+
+ private func resume(with result: Result) {
+ let cont: CheckedContinuation? = lock.withLock {
+ let c = continuation
+ continuation = nil
+ return c
+ }
+
+ switch result {
+ case .success:
+ cont?.resume()
+ case let .failure(error):
+ cont?.resume(throwing: error)
}
- receiptRefreshRequest.cancel()
}
-
- func cancel()
- {
- receiptRefreshRequest.cancel()
- }
}
#endif
-
-public typealias SubscriptionGroup = Set
-
-public extension InAppReceipt
-{
- /// Check whether user is eligible for introductory offer for any products within the same subscription group
- ///
- /// - Returns `false` if user isn't eligible for introductory offer, otherwise `true`
- func isEligibleForIntroductoryOffer(for group: SubscriptionGroup) -> Bool
- {
- let array = purchases.filter { $0.subscriptionTrialPeriod || $0.subscriptionIntroductoryPricePeriod }
- .filter { group.contains($0.productIdentifier) }
-
- return array.isEmpty
- }
-
- /// Check whether user is eligible for introductory offer for a specific product
- ///
- /// - Returns `false` if user isn't eligible for introductory offer, otherwise `true`
- func isEligibleForIntroductoryOffer(for productIdentifier: String) -> Bool
- {
- let array = purchases.filter { $0.subscriptionTrialPeriod || $0.subscriptionIntroductoryPricePeriod }
- .filter { $0.productIdentifier == productIdentifier }
-
- return array.isEmpty
- }
-}
diff --git a/Sources/IARError.swift b/Sources/IARError.swift
deleted file mode 100644
index 5722869..0000000
--- a/Sources/IARError.swift
+++ /dev/null
@@ -1,68 +0,0 @@
-//
-// IARError.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 20/01/17.
-// Copyright © 2017-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-
-/// `IARError` is the error type returned by InAppReceipt.
-/// It encompasses a few different types of errors, each with their own associated reasons.
-///
-/// - initializationFailed: Error occurs during initialization step
-/// - validationFailed: Error occurs during the receipt validation process
-public enum IARError: Error
-{
- case initializationFailed(reason: ReceiptInitializationFailureReason)
- case validationFailed(reason: ValidationFailureReason)
- case purchaseExpired
- case receiptRefreshingInProgress
- /// The underlying reason the receipt initialization error occurred.
- ///
- /// - appStoreReceiptNotFound: In-App Receipt not found
- /// - pkcs7ParsingError: PKCS7 Container can't be extracted from in-app receipt data
- public enum ReceiptInitializationFailureReason
- {
- case appStoreReceiptNotFound
- case pkcs7ParsingError
- case dataIsInvalid
- }
-
- /// The underlying reason the receipt validation error occurred.
- ///
- /// - hashValidation: Computed hash doesn't match the hash from the receipt's payload
- /// - signatureValidation: Error occurs during signature validation. It has several reasons to failure
- public enum ValidationFailureReason
- {
- case hashValidation
- case signatureValidation(SignatureValidationFailureReason)
- case bundleIdentifierVerification
- case bundleVersionVerification
- }
-
- /// The underlying reason the signature validation error occurred.
- ///
- /// - appleIncRootCertificateNotFound: Apple Inc Root Certificate Not Found
- /// - unableToLoadAppleIncRootCertificate: Unable To Load Apple Inc Root Certificate
- /// - receiptIsNotSigned: The receipt doesn't contain a signature
- /// - receiptSignedDataNotFound: The receipt does contain somr signature, but there is an error while creating a signature object
- /// - invalidSignature: The receipt contains invalid signature
- public enum SignatureValidationFailureReason
- {
- case appleIncRootCertificateNotFound
- case unableToLoadAppleIncRootCertificate
- case unableToLoadAppleIncPublicKey
- case unableToLoadiTunesCertificate
- case unableToLoadiTunesPublicKey
- case unableToLoadWorldwideDeveloperCertificate
- case unableToLoadAppleIncPublicSecKey
- case receiptIsNotSigned
- case receiptSignedDataNotFound
- case receiptDataNotFound
- case signatureNotFound
- case invalidSignature
- case invalidCertificateChainOfTrust
- }
-}
diff --git a/Sources/InAppPurchase.swift b/Sources/InAppPurchase.swift
deleted file mode 100644
index 27cd91d..0000000
--- a/Sources/InAppPurchase.swift
+++ /dev/null
@@ -1,101 +0,0 @@
-//
-// InAppPurchase.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 19/01/17.
-// Copyright © 2017-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-
-public struct InAppPurchase
-{
- public enum `Type`: Int32
- {
- /// Type that we can't recognize for some reason
- case unknown = -1
-
- /// Type that customers purchase once. They don't expire.
- case nonConsumable
-
- /// Type that are depleted after one use. Customers can purchase them multiple times.
- case consumable
-
- /// Type that customers purchase once and that renew automatically on a recurring basis until customers decide to cancel.
- case nonRenewingSubscription
-
- /// Type that customers purchase and it provides access over a limited duration and don't renew automatically. Customers can purchase them again.
- case autoRenewableSubscription
- }
-
- /// The product identifier which purchase related to
- public var productIdentifier: String
-
- /// Product type
- public var productType: Type = .unknown
-
- /// Transaction identifier
- public var transactionIdentifier: String
-
- /// Original Transaction identifier
- public var originalTransactionIdentifier: String
-
- /// Purchase Date
- public var purchaseDate: Date
-
- /// Original Purchase Date. Returns `nil` when testing with StoreKitTest
- public var originalPurchaseDate: Date! = nil
-
- /// Subscription Expiration Date. Returns `nil` if the purchase has been expired (in some cases)
- public var subscriptionExpirationDate: Date? = nil
-
- /// Cancellation Date. Returns `nil` if the purchase is not a renewable subscription
- public var cancellationDate: Date? = nil
-
- /// This value is `true`if the customer’s subscription is currently in the free trial period, or `false` if not.
- public var subscriptionTrialPeriod: Bool = false
-
- /// This value is `true` if the customer’s subscription is currently in an introductory price period, or `false` if not.
- public var subscriptionIntroductoryPricePeriod: Bool = false
-
- /// A unique identifier for purchase events across devices, including subscription-renewal events. This value is the primary key for identifying subscription purchases.
- public var webOrderLineItemID: Int? = nil
-
- /// The value is an identifier of the subscription offer that the user redeemed.
- /// Returns `nil` if the user didn't use any subscription offers.
- public var promotionalOfferIdentifier: String? = nil
-
- /// The number of consumable products purchased
- /// The default value is `1` unless modified with a mutable payment. The maximum value is 10.
- public var quantity: Int = 1
-}
-
-public extension InAppPurchase
-{
- /// A Boolean value indicating whether the purchase is renewable subscription.
- var isRenewableSubscription: Bool
- {
- return subscriptionExpirationDate != nil
- }
-
- /// Check whether the subscription is active for a specific date
- ///
- /// - Parameter date: The date in which the auto-renewable subscription should be active.
- /// - Returns: true if the latest auto-renewable subscription is active for the given date, false otherwise.
- func isActiveAutoRenewableSubscription(forDate date: Date) -> Bool
- {
- assert(isRenewableSubscription, "\(productIdentifier) is not an auto-renewable subscription.")
-
- if cancellationDate != nil
- {
- return false
- }
-
- guard let expirationDate = subscriptionExpirationDate else
- {
- return false
- }
-
- return date >= purchaseDate && date < expirationDate
- }
-}
diff --git a/Sources/InAppReceipt+ASN1Decodable.swift b/Sources/InAppReceipt+ASN1Decodable.swift
deleted file mode 100644
index d1ea8f2..0000000
--- a/Sources/InAppReceipt+ASN1Decodable.swift
+++ /dev/null
@@ -1,334 +0,0 @@
-//
-// InAppReceipt+ASN1Decodable.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 04/08/20.
-// Copyright © 2017-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-import ASN1Swift
-
-class _InAppReceipt
-{
- private var pkcs7container: PKCS7Container
-
- var payload: InAppReceiptPayload!
-
- init(rawData: Data) throws
- {
- let asn1decoder = ASN1Decoder()
- self.pkcs7container = try asn1decoder.decode(PKCS7Container.self, from: rawData)
-
- do
- {
- self.payload = try asn1decoder.decode(PayloadContainer.self, from: pkcs7container.signedData.contentInfo.payload.rawData).payload
- }catch{
- self.payload = try asn1decoder.decode(_PayloadContainer.self, from: pkcs7container.signedData.contentInfo.payload.rawData).payload
- }
- }
-}
-
-extension _InAppReceipt
-{
- var digestAlgorithm: SecKeyAlgorithm?
- {
- guard let algName = pkcs7container.signedData.alg.items.first?.algorithm else
- {
- return nil
- }
-
- guard let alg = OID(rawValue: algName)?.encryptionAlgorithm() else
- {
- return nil
- }
-
- return alg
- }
-
- var worldwideDeveloperCertificateData: Data?
- {
- let arr = pkcs7container.signedData.certificates.certificates
-
- guard arr.count >= 2 else
- {
- return nil
- }
-
- return arr[1].rawData
- }
-
- var signatureData: Data
- {
- return pkcs7container.signedData.signerInfos.encryptedDigest
- }
-
- var iTunesCertificateContainer: PKCS7Container.Certificate?
- {
- return pkcs7container.signedData.certificates.certificates.first
- }
-
- var iTunesCertificateData: Data?
- {
- return iTunesCertificateContainer?.rawData
- }
-
- var iTunesPublicKeyData: Data?
- {
- return iTunesCertificateContainer?.cert.subjectPublicKeyInfo
- }
-}
-
-extension InAppReceiptPayload: ASN1Decodable
-{
- static var template: ASN1Template
- {
- return ASN1Template.universal(ASN1Identifier.Tag.octetString)
- }
-
- enum CodingKeys: ASN1CodingKey
- {
- case set
-
- var template: ASN1Template
- {
- return ASN1Template.universal(ASN1Identifier.Tag.set).constructed()
- }
- }
-
- init(from decoder: Decoder) throws
- {
- let asn1d = decoder as! ASN1DecoderProtocol
-
- let rawData: Data = try asn1d.extractValueData()
- var bundleIdentifier = ""
- var bundleIdentifierData = Data()
- var appVersion = ""
- var originalAppVersion = ""
- var originalPurchaseDate: Date?
- var purchases = [InAppPurchase]()
- var opaqueValue = Data()
- var receiptHash = Data()
- var expirationDate: Date?
- var receiptCreationDate: Date!
- var ageRating: String = ""
- var environment: String = ""
-
- let c = try decoder.container(keyedBy: CodingKeys.self)
- var container = try c.nestedUnkeyedContainer(forKey: .set) as! ASN1UnkeyedDecodingContainerProtocol
-
- while !container.isAtEnd
- {
- do
- {
- var attributeContainer = try container.nestedUnkeyedContainer(for: ReceiptAttribute.template) as! ASN1UnkeyedDecodingContainerProtocol
- let type: Int32 = try attributeContainer.decode(Int32.self)
- let _ = try attributeContainer.skip(template: .universal(ASN1Identifier.Tag.integer)) // Consume
- var valueContainer = try attributeContainer.nestedUnkeyedContainer(for: .universal(ASN1Identifier.Tag.octetString)) as! ASN1UnkeyedDecodingContainerProtocol
-
- switch type
- {
- case InAppReceiptField.bundleIdentifier:
- bundleIdentifier = try valueContainer.decode(String.self)
- bundleIdentifierData = valueContainer.valueData
- case InAppReceiptField.appVersion:
- appVersion = try valueContainer.decode(String.self)
- case InAppReceiptField.opaqueValue:
- opaqueValue = valueContainer.valueData
- case InAppReceiptField.receiptHash:
- receiptHash = valueContainer.valueData
- case InAppReceiptField.inAppPurchaseReceipt:
- 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()
- case InAppReceiptField.receiptCreationDate:
- let receiptCreationDateString = try valueContainer.decode(String.self, template: .universal(ASN1Identifier.Tag.ia5String))
- receiptCreationDate = receiptCreationDateString.rfc3339date()
- case InAppReceiptField.ageRating:
- ageRating = try valueContainer.decode(String.self, template: .universal(ASN1Identifier.Tag.ia5String))
- case InAppReceiptField.environment:
- environment = try valueContainer.decode(String.self)
- default:
- break
- }
- }catch{
- assertionFailure("Something wrong here")
- }
- }
-
- self.init(bundleIdentifier: bundleIdentifier,
- appVersion: appVersion,
- originalAppVersion: originalAppVersion,
- originalPurchaseDate: originalPurchaseDate,
- purchases: purchases,
- expirationDate: expirationDate,
- bundleIdentifierData: bundleIdentifierData,
- opaqueValue: opaqueValue,
- receiptHash: receiptHash,
- creationDate: receiptCreationDate,
- ageRating: ageRating,
- environment: environment,
- rawData: rawData)
- }
-}
-
-extension InAppPurchase: ASN1Decodable
-{
- public init(from decoder: Decoder) throws
- {
- var container = try decoder.unkeyedContainer() as! ASN1UnkeyedDecodingContainerProtocol
-
- var originalTransactionIdentifier = ""
- var productIdentifier = ""
- var transactionIdentifier = ""
- var purchaseDate: Date!
- var originalPurchaseDate: Date!
-
- while !container.isAtEnd
- {
- do
- {
- var attributeContainer = try container.nestedUnkeyedContainer(for: ReceiptAttribute.template) as! ASN1UnkeyedDecodingContainerProtocol
- let type: Int32 = try attributeContainer.decode(Int32.self)
- let _ = try attributeContainer.skip(template: .universal(ASN1Identifier.Tag.integer)) // Consume
- var valueContainer = try attributeContainer.nestedUnkeyedContainer(for: .universal(ASN1Identifier.Tag.octetString)) as! ASN1UnkeyedDecodingContainerProtocol
-
- switch type
- {
- case InAppReceiptField.quantity:
- quantity = try valueContainer.decode(Int.self)
- case InAppReceiptField.productIdentifier:
- productIdentifier = try valueContainer.decode(String.self)
- case InAppReceiptField.productType:
- productType = Type(rawValue: try valueContainer.decode(Int32.self)) ?? .unknown
- case InAppReceiptField.transactionIdentifier:
- transactionIdentifier = try valueContainer.decode(String.self)
- case InAppReceiptField.purchaseDate:
- let purchaseDateString = try valueContainer.decode(String.self, template: .universal(ASN1Identifier.Tag.ia5String))
- purchaseDate = purchaseDateString.rfc3339date()
- case InAppReceiptField.originalTransactionIdentifier:
- originalTransactionIdentifier = try valueContainer.decode(String.self)
- case InAppReceiptField.originalPurchaseDate:
- let originalPurchaseDateString = try valueContainer.decode(String.self, template: .universal(ASN1Identifier.Tag.ia5String))
- originalPurchaseDate = originalPurchaseDateString.rfc3339date()
- case InAppReceiptField.subscriptionExpirationDate:
- let str = try valueContainer.decode(String.self, template: .universal(ASN1Identifier.Tag.ia5String))
- let subscriptionExpirationDateString = str == "" ? nil : str
- subscriptionExpirationDate = subscriptionExpirationDateString?.rfc3339date()
- case InAppReceiptField.cancellationDate:
- let str = try valueContainer.decode(String.self, template: .universal(ASN1Identifier.Tag.ia5String))
- let cancellationDateString = str == "" ? nil : str
- cancellationDate = cancellationDateString?.rfc3339date()
- case InAppReceiptField.webOrderLineItemID:
- webOrderLineItemID = try valueContainer.decode(Int.self)
- case InAppReceiptField.subscriptionTrialPeriod:
- subscriptionTrialPeriod = (try valueContainer.decode(Int32.self)) != 0
- case InAppReceiptField.subscriptionIntroductoryPricePeriod:
- subscriptionIntroductoryPricePeriod = (try valueContainer.decode(Int32.self)) != 0
- case InAppReceiptField.promotionalOfferIdentifier:
- promotionalOfferIdentifier = try valueContainer.decode(String.self)
- default:
- break
- }
- }
- }
-
- self.originalTransactionIdentifier = originalTransactionIdentifier
- self.productIdentifier = productIdentifier
- self.transactionIdentifier = transactionIdentifier
- self.purchaseDate = purchaseDate
- self.originalPurchaseDate = originalPurchaseDate
- }
-
- public static var template: ASN1Template
- {
- return ASN1Template.universal(ASN1Identifier.Tag.set).constructed()
- }
-}
-
-struct ReceiptAttribute: ASN1Decodable
-{
- static var template: ASN1Template
- {
- return ASN1Template.universal(ASN1Identifier.Tag.sequence).constructed()
- }
-
- var type: Int
- var version: Int
- var value: Data
-
- enum CodingKeys: ASN1CodingKey
- {
- case type
- case version
- case value
-
- var template: ASN1Template
- {
- switch self
- {
- case .type:
- return .universal(ASN1Identifier.Tag.integer)
- case .version:
- return .universal(ASN1Identifier.Tag.integer)
- case .value:
- return .universal(ASN1Identifier.Tag.octetString)
- }
- }
- }
-}
-
-struct PayloadContainer: ASN1Decodable
-{
- var payload: InAppReceiptPayload
-
- static var template: ASN1Template
- {
- return ASN1Template.contextSpecific(0).constructed().explicit(tag: ASN1Identifier.Tag.octetString).constructed()
- }
-
- enum CodingKeys: ASN1CodingKey
- {
- case payload
-
- var template: ASN1Template
- {
- switch self
- {
- case .payload:
- return InAppReceiptPayload.template
- }
- }
- }
-}
-
-/// Legacy payload format
-struct _PayloadContainer: ASN1Decodable
-{
- var payload: InAppReceiptPayload
-
- static var template: ASN1Template
- {
- return ASN1Template.contextSpecific(0).constructed()
- }
-
- enum CodingKeys: ASN1CodingKey
- {
- case payload
-
- var template: ASN1Template
- {
- switch self
- {
- case .payload:
- return InAppReceiptPayload.template
- }
- }
- }
-}
diff --git a/Sources/InAppReceipt.swift b/Sources/InAppReceipt.swift
deleted file mode 100644
index df0c633..0000000
--- a/Sources/InAppReceipt.swift
+++ /dev/null
@@ -1,317 +0,0 @@
-//
-// InAppReceipt.swift
-// TPReceiptValidator
-//
-// Created by Pavel Tikhonenko on 28/09/16.
-// Copyright © 2016-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-import ASN1Swift
-
-public struct InAppReceiptField
-{
- static let environment: Int32 = 0 // Sandbox, Production, ProductionSandbox
- static let bundleIdentifier: Int32 = 2
- static let appVersion: Int32 = 3
- static let opaqueValue: Int32 = 4
- static let receiptHash: Int32 = 5 // SHA-1 Hash
- 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.
- static let originalAppPurchaseDate: Int32 = 18
- static let originalAppVersion: Int32 = 19
- static let expirationDate: Int32 = 21
-
-
- static let quantity: Int32 = 1701
- static let productIdentifier: Int32 = 1702
- static let transactionIdentifier: Int32 = 1703
- static let purchaseDate: Int32 = 1704
- static let originalTransactionIdentifier: Int32 = 1705
- static let originalPurchaseDate: Int32 = 1706
- static let productType: Int32 = 1707
- static let subscriptionExpirationDate: Int32 = 1708
- static let webOrderLineItemID: Int32 = 1711
- static let cancellationDate: Int32 = 1712
- static let subscriptionTrialPeriod: Int32 = 1713
- static let subscriptionIntroductoryPricePeriod: Int32 = 1719
- static let promotionalOfferIdentifier: Int32 = 1721
-}
-
-public class InAppReceipt
-{
- /// PKCS7 container
- internal var receipt: _InAppReceipt
-
- /// Payload of the receipt.
- /// Payload object contains all meta information.
- internal var payload: InAppReceiptPayload { receipt.payload }
-
- /// root certificate path, used to check signature
- /// added for testing purpose , as unit test can't read main bundle
- internal var rootCertificatePath: String?
-
- /// Raw data
- private var rawData: Data
-
- /// Initialize a `InAppReceipt` using local receipt
- public convenience init() throws
- {
- let data = try Bundle.main.appStoreReceiptData()
- try self.init(receiptData: data)
- }
-
- ///
- ///
- /// Initialize a `InAppReceipt` with asn1 payload
- ///
- /// - parameter receiptData: `Data` object that represents receipt
- public init(receiptData: Data, rootCertPath: String? = nil) throws
- {
- self.receipt = try _InAppReceipt(rawData: receiptData)
- self.rawData = receiptData
-
- #if DEBUG
- let certificateName = "StoreKitTestCertificate"
- #else
- let certificateName = "AppleIncRootCertificate"
- #endif
-
- self.rootCertificatePath = rootCertPath ?? Bundle.lookUp(forResource: certificateName, ofType: "cer")
- }
-}
-
-public extension InAppReceipt
-{
- /// The app’s bundle identifier
- var bundleIdentifier: String
- {
- return payload.bundleIdentifier
- }
-
- /// The app’s version number
- var appVersion: String
- {
- return payload.appVersion
- }
-
- /// The version of the app that was originally purchased.
- var originalAppVersion: String
- {
- 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]
- {
- return payload.purchases
- }
-
- /// Returns all auto renewable `InAppPurchase`s,
- var autoRenewablePurchases: [InAppPurchase]
- {
- return purchases.filter({ $0.isRenewableSubscription })
- }
-
- /// Returns all ACTIVE auto renewable `InAppPurchase`s,
- ///
- var activeAutoRenewableSubscriptionPurchases: [InAppPurchase]
- {
- return purchases.filter({ $0.isRenewableSubscription && $0.isActiveAutoRenewableSubscription(forDate: Date()) })
-
- }
-
- /// The date that the app receipt expires
- var expirationDate: Date?
- {
- return payload.expirationDate
- }
-
- /// Returns `true` if any purchases exist, `false` otherwise
- var hasPurchases: Bool
- {
- return purchases.count > 0
- }
-
- /// Returns `true` if any Active Auto Renewable purchases exist, `false` otherwise
- var hasActiveAutoRenewablePurchases: Bool
- {
- return activeAutoRenewableSubscriptionPurchases.count > 0
- }
-
-
- var creationDate: Date
- {
- return payload.creationDate
- }
-
- var ageRating: String
- {
- return payload.ageRating
- }
-
- /// In App Receipt in base64
- var base64: String
- {
- return rawData.base64EncodedString()
- }
-
- /// Return original transaction identifier if there is a purchase for a specific product identifier
- ///
- /// - parameter productIdentifier: Product name
- func originalTransactionIdentifier(ofProductIdentifier productIdentifier: String) -> String?
- {
- return purchases(ofProductIdentifier: productIdentifier).first?.originalTransactionIdentifier
- }
-
- /// Returns `true` if there is a purchase for a specific product identifier, `false` otherwise
- ///
- /// - parameter productIdentifier: Product name
- func containsPurchase(ofProductIdentifier productIdentifier: String) -> Bool
- {
- for item in purchases
- {
- if item.productIdentifier == productIdentifier
- {
- return true
- }
- }
-
- return false
- }
-
- /// Returns `[InAppPurchase]` if there are purchases for a specific product identifier,
- /// empty array otherwise
- ///
- /// - parameter productIdentifier: Product name
- /// - parameter sort: Sorting block
- func purchases(ofProductIdentifier productIdentifier: String,
- sortedBy sort: ((InAppPurchase, InAppPurchase) -> Bool)? = nil) -> [InAppPurchase]
- {
- let filtered: [InAppPurchase] = purchases.filter({
- return $0.productIdentifier == productIdentifier
- })
-
- if let sort = sort
- {
- return filtered.sorted(by: {
- return sort($0, $1)
- })
- }else{
- return filtered.sorted(by: {
- return $0.purchaseDate > $1.purchaseDate
- })
- }
- }
-
- /// Returns `InAppPurchase` if there is a purchase for a specific product identifier,
- /// `nil` otherwise
- ///
- /// - parameter productIdentifier: Product name
- func activeAutoRenewableSubscriptionPurchases(ofProductIdentifier productIdentifier: String, forDate date: Date) -> InAppPurchase?
- {
- let filtered = purchases(ofProductIdentifier: productIdentifier)
-
- for purchase in filtered
- {
- if purchase.isActiveAutoRenewableSubscription(forDate: date)
- {
- return purchase
- }
- }
-
- return nil
- }
-
- /// Returns the last `InAppPurchase` if there is one for a specific product identifier,
- /// `nil` otherwise
- ///
- /// - parameter productIdentifier: Product name
- func lastAutoRenewableSubscriptionPurchase(ofProductIdentifier productIdentifier: String) -> InAppPurchase?
- {
- var purchase: InAppPurchase? = nil
- let filtered = purchases(ofProductIdentifier: productIdentifier)
-
- var lastInterval: TimeInterval = 0
- for iap in filtered
- {
- if let thisInterval = iap.subscriptionExpirationDate?.timeIntervalSince1970
- {
- if purchase == nil || thisInterval > lastInterval
- {
- purchase = iap
- lastInterval = thisInterval
- }
- }
- }
-
- return purchase
- }
-
- /// Returns true if there is an active subscription for a specific product identifier on the date specified,
- /// false otherwise
- ///
- /// - parameter productIdentifier: Product name
- /// - parameter date: Date to check subscription against
- func hasActiveAutoRenewableSubscription(ofProductIdentifier productIdentifier: String, forDate date: Date) -> Bool
- {
- return activeAutoRenewableSubscriptionPurchases(ofProductIdentifier: productIdentifier, forDate: date) != nil
- }
-}
-
-internal extension InAppReceipt
-{
- /// Used to validate the receipt
- var bundleIdentifierData: Data
- {
- return payload.bundleIdentifierData
- }
-
- /// An opaque value used, with other data, to compute the SHA-1 hash during validation.
- var opaqueValue: Data
- {
- return payload.opaqueValue
- }
-
- /// A SHA-1 hash, used to validate the receipt.
- var receiptHash: Data
- {
- return payload.receiptHash
- }
-}
-
-public extension InAppReceipt
-{
- /// signature for validation
- var signature: Data?
- {
- return receipt.signatureData
- }
-
- var worldwideDeveloperCertificateData: Data?
- {
- return receipt.worldwideDeveloperCertificateData
- }
-
- var iTunesCertificateData: Data?
- {
- return receipt.iTunesCertificateData
- }
-
- var iTunesPublicKeyData: Data?
- {
- return receipt.iTunesPublicKeyData
- }
-
- var payloadRawData: Data
- {
- return payload.rawData
- }
-}
diff --git a/Sources/InAppReceiptPayload.swift b/Sources/InAppReceiptPayload.swift
deleted file mode 100644
index 8e8a557..0000000
--- a/Sources/InAppReceiptPayload.swift
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-// InAppReceiptPayload.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 20/01/17.
-// Copyright © 2017-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-import ASN1Swift
-
-struct InAppReceiptPayload
-{
- /// In-app purchase's receipts
- let purchases: [InAppPurchase]
-
- /// The app’s bundle identifier
- let bundleIdentifier: String
-
- /// The app’s version number
- let appVersion: String
-
- /// 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?
-
- /// Used to validate the receipt
- let bundleIdentifierData: Data
-
- /// An opaque value used, with other data, to compute the SHA-1 hash during validation.
- let opaqueValue: Data
-
- /// A SHA-1 hash, used to validate the receipt.
- let receiptHash: Data
-
- /// The date when the app receipt was created.
- let creationDate: Date
-
- /// Age Rating of the app
- let ageRating: String
-
- /// Receipt's environment
- let environment: String
-
- /// Raw payload data
- let rawData: Data
-
- /// Initialize a `InAppReceipt` passing all values
- ///
- 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
- self.opaqueValue = opaqueValue
- self.receiptHash = receiptHash
- self.creationDate = creationDate
- self.ageRating = ageRating
- self.environment = environment
- self.rawData = rawData
- }
-}
diff --git a/Sources/Initialization.swift b/Sources/Initialization.swift
deleted file mode 100644
index 41c0457..0000000
--- a/Sources/Initialization.swift
+++ /dev/null
@@ -1,91 +0,0 @@
-//
-// Initialization.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 05/02/17.
-// Copyright © 2017-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-
-/// An InAppReceipt extension helps to initialize the receipt
-public extension InAppReceipt
-{
- /// Creates and returns the 'InAppReceipt' instance from data object
- ///
- /// - Returns: 'InAppReceipt' instance
- /// - throws: An error in the InAppReceipt domain, if `InAppReceipt` cannot be created.
- static func receipt(from data: Data) throws -> InAppReceipt
- {
- return try InAppReceipt(receiptData: data)
- }
-
- /// Creates and returns the 'InAppReceipt' instance using local receipt
- ///
- /// - Returns: 'InAppReceipt' instance
- /// - throws: An error in the InAppReceipt domain, if `InAppReceipt` cannot be created.
- static func localReceipt() throws -> InAppReceipt
- {
- let data = try Bundle.main.appStoreReceiptData()
- return try InAppReceipt.receipt(from: data)
- }
-
-}
-
-/// A Bundle extension helps to retrieve receipt data
-public extension Bundle
-{
- /// Retrieve local App Store Receipt Data
- ///
- /// - Returns: 'Data' object that represents local receipt
- /// - throws: An error if receipt file not found or 'Data' can't be created
- func appStoreReceiptData() throws -> Data
- {
- guard let receiptUrl = appStoreReceiptURL,
- FileManager.default.fileExists(atPath: receiptUrl.path) else
- {
- throw IARError.initializationFailed(reason: .appStoreReceiptNotFound)
- }
-
- do {
- return try Data(contentsOf: receiptUrl)
- }catch{
- throw IARError.initializationFailed(reason: .appStoreReceiptNotFound)
- }
- }
-
- /// Retrieve local App Store Receipt Data in base64 string
- ///
- /// - Returns: 'Data' object that represents local receipt
- /// - throws: An error if receipt file not found or 'Data' can't be created
- func appStoreReceiptBase64() throws -> String
- {
- return try appStoreReceiptData().base64EncodedString()
- }
-
- class func lookUp(forResource name: String, ofType ext: String?) -> String?
- {
-
- if let p = Bundle.module.path(forResource: name, ofType: ext)
- {
- return p
- }
-
- if let p = Bundle.main.path(forResource: name, ofType: ext)
- {
- return p
- }
-
- for f in Bundle.allFrameworks
- {
- if let identifier = f.bundleIdentifier, identifier.contains("TPInAppReceipt"),
- let p = f.path(forResource: name, ofType: ext)
- {
- return p
- }
- }
-
- return nil
- }
-}
-
diff --git a/Sources/Objc/InAppReceipt+Objc.swift b/Sources/Objc/InAppReceipt+Objc.swift
deleted file mode 100644
index a3c74c8..0000000
--- a/Sources/Objc/InAppReceipt+Objc.swift
+++ /dev/null
@@ -1,390 +0,0 @@
-//
-// InAppReceipt+Objc.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 24.05.2021.
-// Copyright © 2020-2021 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-import TPInAppReceipt
-
-// MARK: - InAppReceipt
-
-@objc(InAppReceipt) public class InAppReceipt_Objc: NSObject
-{
- private var wrappedReceipt: InAppReceipt
-
- /// Creates and returns the 'InAppReceipt' instance from data object
- ///
- /// - Returns: 'InAppReceipt' instance
- /// - throws: An error in the InAppReceipt domain, if `InAppReceipt` cannot be created.
- @objc public class func receipt(from data: Data) throws -> InAppReceipt_Objc
- {
- return try InAppReceipt_Objc(receiptData: data)
- }
-
- /// Creates and returns the 'InAppReceipt' instance from data object
- ///
- /// - Returns: 'InAppReceipt' instance
- /// - throws: An error in the InAppReceipt domain, if `InAppReceipt` cannot be created.
- @objc public class func receipt(from data: Data) -> InAppReceipt_Objc?
- {
- return try? InAppReceipt_Objc(receiptData: data)
- }
-
- /// Creates and returns the 'InAppReceipt' instance using local receipt
- ///
- /// - Returns: 'InAppReceipt' instance
- /// - throws: An error in the InAppReceipt domain, if `InAppReceipt` cannot be created.
- @objc public class func local() throws -> InAppReceipt_Objc
- {
- let data = try Bundle.main.appStoreReceiptData()
- return try InAppReceipt_Objc.receipt(from: data)
- }
-
- /// Creates and returns the 'InAppReceipt' instance using local receipt
- ///
- /// - Returns: 'InAppReceipt' instance
- /// - throws: An error in the InAppReceipt domain, if `InAppReceipt` cannot be created.
- @objc public class func local() -> InAppReceipt_Objc?
- {
- guard let data = try? Bundle.main.appStoreReceiptData() else { return nil }
- return InAppReceipt_Objc.receipt(from: data)
- }
-
- ///
- ///
- /// Initialize a `InAppReceipt` with asn1 payload
- ///
- /// - parameter receiptData: `Data` object that represents receipt
- @objc public init(receiptData: Data, rootCertPath: String? = nil) throws
- {
- self.wrappedReceipt = try InAppReceipt.init(receiptData: receiptData, rootCertPath: rootCertPath)
- }
-}
-
-@objc public extension InAppReceipt_Objc
-{
- /// The app’s bundle identifier
- var bundleIdentifier: String
- {
- return wrappedReceipt.bundleIdentifier
- }
-
- /// The app’s version number
- var appVersion: String
- {
- return wrappedReceipt.appVersion
- }
-
- /// The version of the app that was originally purchased.
- var originalAppVersion: String
- {
- 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]
- {
- return wrappedReceipt.purchases.map { .init(purchase: $0) }
- }
-
- /// Returns all auto renewable `InAppPurchase`s,
- var autoRenewablePurchases: [InAppPurchase_Objc]
- {
- return wrappedReceipt.purchases.filter({ $0.isRenewableSubscription }).map { .init(purchase: $0) }
- }
-
- /// Returns all ACTIVE auto renewable `InAppPurchase`s,
- ///
- var activeAutoRenewableSubscriptionPurchases: [InAppPurchase_Objc]
- {
- return wrappedReceipt.purchases.filter({ $0.isRenewableSubscription && $0.isActiveAutoRenewableSubscription(forDate: Date()) }).map { .init(purchase: $0) }
-
- }
-
- /// The date that the app receipt expires
- var expirationDate: Date?
- {
- return wrappedReceipt.expirationDate
- }
-
- /// Returns `true` if any purchases exist, `false` otherwise
- var hasPurchases: Bool
- {
- return purchases.count > 0
- }
-
- /// Returns `true` if any Active Auto Renewable purchases exist, `false` otherwise
- var hasActiveAutoRenewablePurchases: Bool
- {
- return activeAutoRenewableSubscriptionPurchases.count > 0
- }
-
-
- var creationDate: Date
- {
- return wrappedReceipt.creationDate
- }
-
- var ageRating: String
- {
- return wrappedReceipt.ageRating
- }
- /// In App Receipt in base64
- var base64: String
- {
- return wrappedReceipt.base64
- }
-
- /// Return original transaction identifier if there is a purchase for a specific product identifier
- ///
- /// - parameter productIdentifier: Product name
- @objc func originalTransactionIdentifier(ofProductIdentifier productIdentifier: String) -> String?
- {
- return purchases(ofProductIdentifier: productIdentifier).first?.originalTransactionIdentifier
- }
-
- /// Returns `true` if there is a purchase for a specific product identifier, `false` otherwise
- ///
- /// - parameter productIdentifier: Product name
- @objc func containsPurchase(ofProductIdentifier productIdentifier: String) -> Bool
- {
- for item in purchases
- {
- if item.productIdentifier == productIdentifier
- {
- return true
- }
- }
-
- return false
- }
-
- /// Returns `[InAppPurchase]` if there are purchases for a specific product identifier,
- /// empty array otherwise
- ///
- /// - parameter productIdentifier: Product name
- /// - parameter sort: Sorting block
- @objc func purchases(ofProductIdentifier productIdentifier: String,
- sortedBy sort: ((InAppPurchase_Objc, InAppPurchase_Objc) -> Bool)? = nil) -> [InAppPurchase_Objc]
- {
- let filtered: [InAppPurchase_Objc] = purchases.filter({
- return $0.productIdentifier == productIdentifier
- })
-
- if let sort = sort
- {
- return filtered.sorted(by: {
- return sort($0, $1)
- })
- }else{
- return filtered.sorted(by: {
- return $0.purchaseDate > $1.purchaseDate
- })
- }
- }
-
- /// Returns `InAppPurchase` if there is a purchase for a specific product identifier,
- /// `nil` otherwise
- ///
- /// - parameter productIdentifier: Product name
- @objc func activeAutoRenewableSubscriptionPurchases(ofProductIdentifier productIdentifier: String, forDate date: Date) -> InAppPurchase_Objc?
- {
- let filtered = purchases(ofProductIdentifier: productIdentifier)
-
- for purchase in filtered
- {
- if purchase.isActiveAutoRenewableSubscription(forDate: date)
- {
- return purchase
- }
- }
-
- return nil
- }
-
- /// Returns the last `InAppPurchase` if there is one for a specific product identifier,
- /// `nil` otherwise
- ///
- /// - parameter productIdentifier: Product name
- @objc func lastAutoRenewableSubscriptionPurchase(ofProductIdentifier productIdentifier: String) -> InAppPurchase_Objc?
- {
- var purchase: InAppPurchase_Objc? = nil
- let filtered = purchases(ofProductIdentifier: productIdentifier)
-
- var lastInterval: TimeInterval = 0
- for iap in filtered
- {
- if !(iap.productIdentifier == productIdentifier) {
- continue
- }
-
- if let thisInterval = iap.subscriptionExpirationDate?.timeIntervalSince1970 {
- if purchase == nil || thisInterval > lastInterval {
- purchase = iap
- lastInterval = thisInterval
- }
- }
- }
-
- return purchase
- }
-
- /// Returns true if there is an active subscription for a specific product identifier on the date specified,
- /// false otherwise
- ///
- /// - parameter productIdentifier: Product name
- /// - parameter date: Date to check subscription against
- @objc func hasActiveAutoRenewableSubscription(ofProductIdentifier productIdentifier: String, forDate date: Date) -> Bool
- {
- return activeAutoRenewableSubscriptionPurchases(ofProductIdentifier: productIdentifier, forDate: date) != nil
- }
-}
-
-// MARK: - InAppPurchase
-
-@objc(InAppPurchase) public class InAppPurchase_Objc: NSObject
-{
- @objc public enum `Type`: Int32
- {
- /// Type that we can't recognize for some reason
- case unknown = -1
-
- /// Type that customers purchase once. They don't expire.
- case nonConsumable
-
- /// Type that are depleted after one use. Customers can purchase them multiple times.
- case consumable
-
- /// Type that customers purchase once and that renew automatically on a recurring basis until customers decide to cancel.
- case nonRenewingSubscription
-
- /// Type that customers purchase and it provides access over a limited duration and don't renew automatically. Customers can purchase them again.
- case autoRenewableSubscription
- }
-
- private let purchase: InAppPurchase
-
- /// The product identifier which purchase related to
- @objc public var productIdentifier: String { purchase.productIdentifier }
-
- /// Product type
- @objc public var productType: Type { Type(rawValue: purchase.productType.rawValue) ?? .unknown }
-
- /// Transaction identifier
- @objc public var transactionIdentifier: String { purchase.transactionIdentifier }
-
- /// Original Transaction identifier
- @objc public var originalTransactionIdentifier: String { purchase.originalTransactionIdentifier }
-
- /// Purchase Date
- @objc public var purchaseDate: Date { purchase.purchaseDate }
-
- /// Original Purchase Date
- @objc public var originalPurchaseDate: Date { purchase.originalPurchaseDate }
-
- /// Subscription Expiration Date. Returns `nil` if the purchase has been expired (in some cases)
- @objc public var subscriptionExpirationDate: Date? { purchase.subscriptionExpirationDate }
-
- /// Cancellation Date. Returns a value if the transaction was refunded by customer support or the auto-renewable subscription plan was upgraded. Otherwise returns nil.
- @objc public var cancellationDate: Date? { purchase.cancellationDate }
-
- /// This value is `true`if the customer’s subscription is currently in the free trial period, or `false` if not.
- @objc public var subscriptionTrialPeriod: Bool { purchase.subscriptionTrialPeriod }
-
- /// This value is `true` if the customer’s subscription is currently in an introductory price period, or `false` if not.
- @objc public var subscriptionIntroductoryPricePeriod: Bool { purchase.subscriptionIntroductoryPricePeriod }
-
- /// A unique identifier for purchase events across devices, including subscription-renewal events. This value is the primary key for identifying subscription purchases.
- @objc public var webOrderLineItemID: Int { purchase.webOrderLineItemID ?? NSNotFound }
-
- /// The value is an identifier of the subscription offer that the user redeemed.
- /// Returns `nil` if the user didn't use any subscription offers.
- @objc public var promotionalOfferIdentifier: String? { purchase.promotionalOfferIdentifier }
-
- /// The number of consumable products purchased
- /// The default value is `1` unless modified with a mutable payment. The maximum value is 10.
- @objc public var quantity: Int { purchase.quantity }
-
- init(purchase: InAppPurchase)
- {
- self.purchase = purchase
- }
-}
-
-@objc public extension InAppPurchase_Objc
-{
- /// A Boolean value indicating whether the purchase is renewable subscription.
- @objc var isRenewableSubscription: Bool
- {
- return purchase.isRenewableSubscription
- }
-
- /// Check whether the subscription is active for a specific date
- ///
- /// - Parameter date: The date in which the auto-renewable subscription should be active.
- /// - Returns: true if the latest auto-renewable subscription is active for the given date, false otherwise.
- @objc func isActiveAutoRenewableSubscription(forDate date: Date) -> Bool
- {
- return purchase.isActiveAutoRenewableSubscription(forDate: date)
- }
-}
-
-// MARK: - Validation
-
-/// A InAppReceipt extension helps to validate the receipt
-@objc public extension InAppReceipt_Objc
-{
- /// Verify In App Receipt
- ///
- /// - throws: An error in the InAppReceipt domain, if verification fails
- @objc func verify() throws
- {
- try wrappedReceipt.verifyHash()
- try wrappedReceipt.verifyBundleIdentifierAndVersion()
- try wrappedReceipt.verifySignature()
- }
-
- /// Verify only hash
- /// Should be equal to `receiptHash` value
- ///
- /// - throws: An error in the InAppReceipt domain, if verification fails
- @objc func verifyHash() throws
- {
- try wrappedReceipt.verifyHash()
- }
-
- /// Verify that the bundle identifier in the receipt matches a hard-coded constant containing the CFBundleIdentifier value you expect in the Info.plist file. If they do not match, validation fails.
- /// Verify that the version identifier string in the receipt matches a hard-coded constant containing the CFBundleShortVersionString value (for macOS) or the CFBundleVersion value (for iOS) that you expect in the Info.plist file.
- ///
- ///
- /// - throws: An error in the InAppReceipt domain, if verification fails
- @objc func verifyBundleIdentifierAndVersion() throws
- {
- try wrappedReceipt.verifyBundleIdentifierAndVersion()
- }
-
- /// Verify signature inside pkcs7 container
- ///
- /// - throws: An error in the InAppReceipt domain, if verification can't be completed
- @objc func verifySignature() throws
- {
- try wrappedReceipt.verifySignature()
- }
-
- /// Check whether user is eligible for introductory offer for a specific product
- ///
- /// - Returns `false` if user isn't eligible for introductory offer, otherwise `true`
- @objc func productIsEligibleForIntroductoryOffer(_ productIdentifier: String) -> Bool
- {
- return wrappedReceipt.isEligibleForIntroductoryOffer(for: productIdentifier)
- }
-}
diff --git a/Sources/AppleIncRootCertificate.cer b/Sources/Resources/AppleIncRootCertificate.cer
similarity index 100%
rename from Sources/AppleIncRootCertificate.cer
rename to Sources/Resources/AppleIncRootCertificate.cer
diff --git a/Sources/PrivacyInfo.xcprivacy b/Sources/Resources/PrivacyInfo.xcprivacy
similarity index 100%
rename from Sources/PrivacyInfo.xcprivacy
rename to Sources/Resources/PrivacyInfo.xcprivacy
diff --git a/Sources/StoreKitTestCertificate.cer b/Sources/Resources/StoreKitTestCertificate.cer
similarity index 100%
rename from Sources/StoreKitTestCertificate.cer
rename to Sources/Resources/StoreKitTestCertificate.cer
diff --git a/Sources/Validation.swift b/Sources/Validation.swift
index daedfc0..18faa6d 100644
--- a/Sources/Validation.swift
+++ b/Sources/Validation.swift
@@ -1,361 +1,112 @@
-//
-// Validation.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 19/01/17.
-// Copyright © 2017-2021 Pavel Tikhonenko. All rights reserved.
-//
+import Foundation
+import X509
-#if canImport(UIKit)
-import UIKit
-#endif
+// MARK: - AppReceipt Validation
-#if canImport(WatchKit)
-import WatchKit
-#endif
+extension AppReceipt {
+ /// Validates the receipt asynchronously.
+ ///
+ /// Performs validation of this receipt instance including certificate chain verification,
+ /// signature verification, hash verification, and metadata validation.
+ ///
+ /// - Parameters:
+ /// - rootCertificate: Optional custom root certificate data. If not provided, the appropriate
+ /// Apple root certificate is selected based on the receipt's environment.
+ /// - deviceIdentifier: Optional device identifier data. If not provided, the system device
+ /// identifier is retrieved asynchronously.
+ /// - Returns: A ``VerificationResult`` indicating whether the receipt is valid or invalid.
+ @concurrent public func validate(
+ rootCertificate: Data? = nil,
+ deviceIdentifier: Data? = nil
+ ) async -> VerificationResult {
+ guard hasValidStructure else {
+ return .invalid(ReceiptValidatorError.invalidReceiptStructure)
+ }
-#if canImport(Cocoa)
-import Cocoa
-import IOKit
-#endif
+ guard let root = await finalAppleRootCertificateData(using: rootCertificate),
+ let rootCert = try? Certificate(derEncoded: root)
+ else {
+ return .invalid(ReceiptValidatorError.rootCertificateInvalid(nil))
+ }
-import CommonCrypto
+ guard let deviceId = await finalDeviceIdentifier(using: deviceIdentifier) else {
+ return .invalid(ReceiptValidatorError.deviceIdentifierIsNotFound)
+ }
-/// A InAppReceipt extension helps to validate the receipt
-public extension InAppReceipt
-{
- /// Determine whether receipt is valid or not
- ///
- /// - Returns:`true` if the receipt is valid, otherwise `false`
- var isValid: Bool
- {
- do {
- try validate()
- return true
- } catch {
- return false
- }
- }
-
- /// Validate In App Receipt
- ///
- /// - throws: An error in the InAppReceipt domain, if verification fails
- func validate() throws
- {
- try verifyHash()
- try verifyBundleIdentifierAndVersion()
- try verifySignature()
- }
-
- /// Verify In App Receipt
- ///
- /// - throws: An error in the InAppReceipt domain, if verification fails
- @available(*, deprecated, renamed: "validate")
- func verify() throws
- {
- try verifyHash()
- try verifyBundleIdentifierAndVersion()
- try verifySignature()
+ let validator = ReceiptValidator.default(
+ rootCertificate: rootCert,
+ deviceIdentifier: deviceId
+ )
+
+ return await validator.validate(self)
}
-
- /// Verify only hash
- /// Should be equal to `receiptHash` value
- ///
- /// - throws: An error in the InAppReceipt domain, if verification fails
- func verifyHash() throws
- {
- if (computedHash != receiptHash)
- {
- throw IARError.validationFailed(reason: .hashValidation)
- }
+}
+
+extension AppReceipt: ReceiptValidatable {
+ public var bundleIdentifierData: Data {
+ payload.bundleIdentifierData
}
-
- /// Verify that the bundle identifier in the receipt matches a hard-coded constant containing the CFBundleIdentifier value you expect in the Info.plist file. If they do not match, validation fails.
- /// Verify that the version identifier string in the receipt matches a hard-coded constant containing the CFBundleShortVersionString value (for macOS) or the CFBundleVersion value (for iOS) that you expect in the Info.plist file.
- ///
- ///
- /// - throws: An error in the InAppReceipt domain, if verification fails
- func verifyBundleIdentifierAndVersion() throws
- {
- try verifyBundleIdentifier()
- try verifyBundleVersion()
+
+ public var opaqueValue: Data {
+ payload.opaqueValue
}
-
- /// Verify that the bundle identifier in the receipt matches a hard-coded constant containing the CFBundleIdentifier value you expect in the Info.plist file. If they do not match, validation fails.
- ///
- ///
- /// - throws: An error in the InAppReceipt domain, if verification fails
- func verifyBundleIdentifier() throws
- {
- guard let bid = Bundle.main.bundleIdentifier,
- bid == bundleIdentifier else
- {
- throw IARError.validationFailed(reason: .bundleIdentifierVerification)
- }
- }
-
- /// Verify that the version identifier string in the receipt matches a hard-coded constant containing the CFBundleShortVersionString value (for macOS) or the CFBundleVersion value (for iOS) that you expect in the Info.plist file.
- ///
- ///
- /// - throws: An error in the InAppReceipt domain, if verification fails
- func verifyBundleVersion() throws
- {
- guard appVersion == Bundle.main.appVersion else
- {
- throw IARError.validationFailed(reason: .bundleVersionVerification)
- }
- }
-
- /// Verify signature inside pkcs7 container
- ///
- /// - throws: An error in the InAppReceipt domain, if verification can't be completed
- func verifySignature() throws
- {
- try checkAppleRootCertExistence()
- try checkSignatureValidity()
- try checkChainOfTrust()
+
+ public var receiptHash: Data {
+ payload.receiptHash
}
-
- /// 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 rootCertificatePath,
- FileManager.default.fileExists(atPath: rootCertificatePath) else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.appleIncRootCertificateNotFound))
- }
+
+ public var versionIdentifier: String {
+ appVersion
}
-
- func checkChainOfTrust() throws
- {
- // Validate chain of trust of certificate
- // Ensure the iTunes certificate included in the receipt is indeed signed by Apple root cert
- // https://developer.apple.com/documentation/security/certificate_key_and_trust_services/trust/creating_a_trust_object
-
- // root cert data is loaded from the bundled Apple Root Certificate
- guard let path = rootCertificatePath,
- let rootCertData = try? Data(contentsOf: URL(fileURLWithPath: path)) else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.unableToLoadAppleIncRootCertificate))
- }
-
- guard let iTunesCertData = iTunesCertificateData else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.unableToLoadiTunesCertificate))
- }
-
- guard let worldwideDeveloperCertData = worldwideDeveloperCertificateData else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.unableToLoadWorldwideDeveloperCertificate))
- }
-
- guard let rootCertSec = SecCertificateCreateWithData(nil, rootCertData as CFData) else {
- throw IARError.validationFailed(reason: .signatureValidation(.unableToLoadAppleIncRootCertificate))
- }
-
- guard let iTunesCertSec = SecCertificateCreateWithData(nil, iTunesCertData as CFData) else {
- throw IARError.validationFailed(reason: .signatureValidation(.unableToLoadiTunesCertificate))
- }
-
- guard let worldwideDevCertSec = SecCertificateCreateWithData(nil, worldwideDeveloperCertData as CFData) else {
- throw IARError.validationFailed(reason: .signatureValidation(.unableToLoadWorldwideDeveloperCertificate))
- }
-
- let policy = SecPolicyCreateBasicX509()
-
- var wwdcTrust: SecTrust?
- var iTunesTrust: SecTrust?
-
- // verify worldwide developer cert in the receipt is signed by Apple Root Cert
- let worldwideDevCertVerifyStatus = SecTrustCreateWithCertificates([worldwideDevCertSec, rootCertSec] as AnyObject,
- policy,
- &wwdcTrust)
-
- 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,
- policy,
- &iTunesTrust)
-
- guard iTunesCertVerifyStatus == errSecSuccess, let iTunesTrust else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust))
- }
-
- var secTrustResult: SecTrustResultType = SecTrustResultType.unspecified
-
- if #available(OSX 10.14, iOS 12.0, tvOS 12.0, *)
- {
- var error: CFError?
- guard SecTrustEvaluateWithError(wwdcTrust, &error) else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust))
- }
- } else {
- guard SecTrustEvaluate(wwdcTrust, &secTrustResult) == errSecSuccess else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust))
- }
- }
-
- if #available(OSX 10.14, iOS 12.0, tvOS 12.0, *)
- {
- var error: CFError?
- guard SecTrustEvaluateWithError(iTunesTrust, &error) else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust))
- }
- } else {
- guard SecTrustEvaluate(iTunesTrust, &secTrustResult) == errSecSuccess else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.invalidCertificateChainOfTrust))
- }
- }
+
+ public var certificates: [Certificate] {
+ content.certificates
}
-
- func checkSignatureValidity() throws
- {
- guard let signature = signature else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.signatureNotFound))
- }
-
- guard let iTunesPublicKeyContainer = receipt.iTunesPublicKeyData else
- {
- throw IARError.validationFailed(reason: .signatureValidation(.unableToLoadiTunesPublicKey))
- }
-
- let keyDict: [String:Any] =
- [
- kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
- kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
- kSecAttrKeySizeInBits as String: 2048,
- ]
- guard let iTunesPublicKeySec = SecKeyCreateWithData(iTunesPublicKeyContainer as CFData, keyDict as CFDictionary, nil) else {
- throw IARError.validationFailed(reason: .signatureValidation(.unableToLoadAppleIncPublicSecKey))
- }
-
- var umErrorCF: Unmanaged? = nil
- guard let alg = receipt.digestAlgorithm,
- SecKeyVerifySignature(iTunesPublicKeySec, alg, payloadRawData as CFData, signature as CFData, &umErrorCF) else {
-
- let error = umErrorCF?.takeRetainedValue() as Error? as NSError?
- print("error is \(String(describing: error))")
-
- throw IARError.validationFailed(reason: .signatureValidation(.invalidSignature))
- }
-
+ public var certificatesRaw: [Data] {
+ content.certificatesRaw
}
-
- /// Computed SHA-1 hash, used to validate the receipt.
- internal var computedHash: Data
- {
- let uuidData = guid()
- let opaqueData = opaqueValue
- let bundleIdData = bundleIdentifierData
-
- var hash = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
- var ctx = CC_SHA1_CTX()
- CC_SHA1_Init(&ctx)
- CC_SHA1_Update(&ctx, uuidData.bytes, CC_LONG(uuidData.count))
- CC_SHA1_Update(&ctx, opaqueData.bytes, CC_LONG(opaqueData.count))
- CC_SHA1_Update(&ctx, bundleIdData.bytes, CC_LONG(bundleIdData.count))
- CC_SHA1_Final(&hash, &ctx)
-
- return Data(hash)
+
+ public var validationTime: Date {
+ creationDate
}
-}
-fileprivate func guid() -> Data
-{
-#if targetEnvironment(macCatalyst) || os(macOS)
- if let guid = getMacAddress()
- {
- return guid
- }else{
- assertionFailure("Failed to retrieve guid")
+ public var digestData: Data {
+ payloadRawData
}
-
- return Data() // Never get called
-#else
-#if canImport(WatchKit)
- var uuidBytes = WKInterfaceDevice.current().identifierForVendor!.uuid
-#elseif canImport(UIKit)
- var uuidBytes = UIDevice.current.identifierForVendor!.uuid
-#endif
-
- return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes))
-#endif
-}
+ public var digestAlgorithm: DigestAlgorithm {
+ signerInfo.digestAlgorithm == .sha1 ? .sha1 : .sha256
+ }
+
+ public var signature: Data {
+ Data(signerInfo.signature.bytes)
+ }
+
+ // MARK: - Private Implementation
+
+ var signerInfo: SignerInfo {
+ guard let signer = content.signerInfos.first else {
+ fatalError("Invalid Receipt: Expected one signer")
+ }
-#if targetEnvironment(macCatalyst) || os(macOS)
-func getMacAddress() -> Data?
-{
- guard let service = ioService(named: "en0", wantBuiltIn: true)
- ?? ioService(named: "en1", wantBuiltIn: true)
- ?? ioService(named: "en0", wantBuiltIn: false)
- else { return nil }
-
- defer { IOObjectRelease(service) }
-
- if let cftype = IORegistryEntrySearchCFProperty(service, kIOServicePlane, "IOMACAddress" as CFString, kCFAllocatorDefault, IOOptionBits(kIORegistryIterateRecursively | kIORegistryIterateParents))
- {
- return (cftype as? Data)
- }
-
- return nil
+ return signer
+ }
}
-func ioService(named name: String, wantBuiltIn: Bool) -> io_service_t?
-{
- let main_port: mach_port_t
- if #available(macOS 12.0, macCatalyst 15.0, *) {
- main_port = kIOMainPortDefault
- } else {
- main_port = 0 // the kIOMasterPortDefault symbol is unavailable on xcode 14 and later.
- }
- var iterator = io_iterator_t()
-
- defer
- {
- if iterator != IO_OBJECT_NULL
- {
- IOObjectRelease(iterator)
- }
- }
-
- guard let matchingDict = IOBSDNameMatching(main_port, 0, name),
- IOServiceGetMatchingServices(main_port, matchingDict as CFDictionary, &iterator) == KERN_SUCCESS,
- iterator != IO_OBJECT_NULL
- else
- {
- return nil
- }
-
- var candidate = IOIteratorNext(iterator)
- while candidate != IO_OBJECT_NULL
- {
- if let cftype = IORegistryEntryCreateCFProperty(candidate, "IOBuiltin" as CFString, kCFAllocatorDefault, 0)
- {
- let isBuiltIn = cftype.takeRetainedValue() as! CFBoolean
- if wantBuiltIn == CFBooleanGetValue(isBuiltIn)
- {
- return candidate
- }
- }
-
- IOObjectRelease(candidate)
- candidate = IOIteratorNext(iterator)
- }
-
- return nil
+extension AppReceipt {
+ fileprivate func finalAppleRootCertificateData(using rootCertificate: Data? = nil) async -> Data? {
+ guard let rootCertificate else {
+ let isXcode = payload.environment == .xcode
+ return await Bundle.appleRootCertificateData(testing: isXcode)
+ }
+ return rootCertificate
+ }
+
+ fileprivate func finalDeviceIdentifier(using deviceIdentifier: Data?) async -> Data? {
+ guard let deviceIdentifier else {
+ return await DeviceIdentifier.data
+ }
+ return deviceIdentifier
+ }
}
-#endif
diff --git a/Sources/Validator/Default Verifiers/Chain/ReceiptChainVerifier.swift b/Sources/Validator/Default Verifiers/Chain/ReceiptChainVerifier.swift
new file mode 100644
index 0000000..f273903
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Chain/ReceiptChainVerifier.swift
@@ -0,0 +1,71 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+import X509
+
+/// Errors that occur during certificate chain verification.
+///
+/// Represents failures in validating the X.509 certificate chain from the leaf certificate to the root.
+public enum ChainVerificationError: Hashable, Sendable, Error {
+ /// The certificate data is invalid, malformed, or cannot be decoded.
+ case invalidCertificateData
+
+ /// The certificate chain validation failed.
+ ///
+ /// This can occur when certificates are expired, self-signed, untrusted, or the chain is incomplete.
+ case chainValidationFailed
+
+ /// The certificate revocation check failed or the certificate has been revoked.
+ case revocationCheckFailed
+}
+
+/// Policies that control how certificate chain verification is performed.
+///
+/// These policies specify validation rules and constraints applied during certificate chain verification.
+public enum ReceiptChainVerifierPolicy: Sendable {
+ /// Use Apple's basic X.509 validation policies for certificate verification.
+ case appleX509Basic
+
+ /// Use App Store specific validation policies.
+ ///
+ /// - Note: This policy doesn't work in Xcode development environments.
+ case appStoreReceipt
+
+ /// Validate the certificate chain as if it was on the specified date.
+ ///
+ /// Ensures certificates were valid on the given date, useful for historical receipt validation.
+ case validationTime(Date)
+
+ /// Use the specified date for online revocation checks, if any.
+ case onlineValidationTime(Date)
+}
+
+/// A protocol for types that verify certificate chains in receipts.
+///
+/// Conforming types validate the X.509 certificate chain used to sign a receipt, ensuring that
+/// the leaf certificate chains up to the Apple root certificate and that all certificates in the
+/// chain are valid, properly chained, and not revoked.
+///
+/// - Note: All types conforming to this protocol must be `Sendable`.
+public protocol ReceiptChainVerifier: Sendable {
+ /// Verifies the certificate chain asynchronously.
+ ///
+ /// Validates that the leaf certificate is properly chained to the intermediate certificate(s),
+ /// and that the chain is valid according to the specified policies.
+ ///
+ /// - Parameters:
+ /// - leaf: The leaf certificate from the receipt to verify.
+ /// - intermediate: Array of intermediate certificates in the chain. Should include all
+ /// certificates between the leaf and the root (typically one intermediate for App Store receipts).
+ /// - policy: Array of verification policies to apply. Controls how validation is performed
+ /// (e.g., which X.509 policies to use, what date to validate against).
+ /// - Returns: A verification result indicating whether the chain is valid or the error that occurred.
+ func verify(
+ leaf: Certificate,
+ intermediate: [Certificate],
+ policy: [ReceiptChainVerifierPolicy]
+ ) async -> VerificationResult
+}
diff --git a/Sources/Validator/Default Verifiers/Chain/Sec_ChainVerifier.swift b/Sources/Validator/Default Verifiers/Chain/Sec_ChainVerifier.swift
new file mode 100644
index 0000000..b4676e1
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Chain/Sec_ChainVerifier.swift
@@ -0,0 +1,153 @@
+#if canImport(Security)
+
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+@preconcurrency import Security
+import X509
+
+// MARK: - "Legacy" Chain Verifier
+
+public struct SecChainVerifier: ReceiptChainVerifier, Sendable {
+ private let rootCertificates: [Data]
+
+ public init(rootCertificates: [Data]) {
+ self.rootCertificates = rootCertificates
+ }
+
+ func verify(
+ leaf: SecCertificate,
+ intermediate: [SecCertificate],
+ policy: [ReceiptChainVerifierPolicy]
+ ) -> VerificationResult {
+ var trust: SecTrust?
+
+ var rootSecCertificates: [SecCertificate] = []
+ for cert in rootCertificates {
+ guard let cert = SecCertificateCreateWithData(nil, cert as CFData) else {
+ return .invalid(ChainVerificationError.invalidCertificateData)
+ }
+ rootSecCertificates.append(cert)
+ }
+
+ var certificates: [SecCertificate] = [leaf]
+ certificates.append(contentsOf: intermediate)
+
+ let status = SecTrustCreateWithCertificates(certificates as CFArray, nil, &trust)
+ guard status == errSecSuccess, let trustRef = trust else {
+ return .invalid(ChainVerificationError.chainValidationFailed)
+ }
+
+ // Set anchor certificates and use only these anchors (not system roots)
+ guard SecTrustSetAnchorCertificates(trustRef, rootSecCertificates as CFArray) == errSecSuccess else {
+ return .invalid(ChainVerificationError.chainValidationFailed)
+ }
+
+ guard SecTrustSetAnchorCertificatesOnly(trustRef, true) == errSecSuccess else {
+ return .invalid(ChainVerificationError.chainValidationFailed)
+ }
+
+ var secPolicies: [SecPolicy] = []
+
+ for p in policy {
+ if let secPolicy = p.policy {
+ secPolicies.append(secPolicy)
+ } else if case let .validationTime(data) = p {
+ if SecTrustSetVerifyDate(trustRef, data as CFDate) != errSecSuccess {
+ return .invalid(ChainVerificationError.chainValidationFailed)
+ }
+ }
+ }
+
+ guard !secPolicies.isEmpty else {
+ return .invalid(ChainVerificationError.chainValidationFailed)
+ }
+
+ guard SecTrustSetPolicies(trustRef, secPolicies as CFArray) == errSecSuccess else {
+ return .invalid(ChainVerificationError.chainValidationFailed)
+ }
+
+ var error: CFError?
+ let trusted = SecTrustEvaluateWithError(trustRef, &error)
+
+ guard trusted && error == nil else {
+ return .invalid(ChainVerificationError.chainValidationFailed)
+ }
+
+ return .valid
+ }
+
+ public func verify(
+ leaf: Certificate,
+ intermediate: [Certificate],
+ policy: [ReceiptChainVerifierPolicy]
+ ) -> VerificationResult {
+ do {
+ let leaf = try SecCertificate.makeWithCertificate(leaf)
+ let intermediate = try intermediate.map { try SecCertificate.makeWithCertificate($0) }
+
+ return verify(
+ leaf: leaf,
+ intermediate: intermediate,
+ policy: policy
+ )
+ } catch {
+ return .invalid(ChainVerificationError.invalidCertificateData)
+ }
+ }
+}
+
+extension SecChainVerifier: ReceiptVerifier {
+ public func verify(_ receipt: any ReceiptValidatable) -> VerificationResult {
+ guard let leaf = receipt.leafCertificate else {
+ return .invalid(ChainVerificationError.invalidCertificateData)
+ }
+
+ let policy: [ReceiptChainVerifierPolicy]
+ if receipt.environment == .xcode {
+ policy = [
+ .appleX509Basic,
+ .validationTime(receipt.validationTime),
+ ]
+ } else {
+ policy = [
+ .appleX509Basic,
+ .appStoreReceipt,
+ .validationTime(receipt.validationTime),
+ ]
+ }
+
+ return verify(
+ leaf: leaf,
+ intermediate: receipt.intermediateCertificates,
+ policy: policy
+ )
+ }
+}
+
+extension ReceiptChainVerifierPolicy {
+ fileprivate var policy: SecPolicy? {
+ switch self {
+ case .appleX509Basic:
+ return SecPolicyCreateWithProperties(kSecPolicyAppleX509Basic, nil)
+ case .appStoreReceipt:
+ return SecPolicyCreateWithProperties(kSecPolicyMacAppStoreReceipt, nil)
+ case .validationTime:
+ return nil
+ case .onlineValidationTime:
+ let revocationOptions: [CFString: Any] = [
+ kSecPolicyRevocationFlags: kSecRevocationOCSPMethod | kSecRevocationCRLMethod
+ ]
+
+ return SecPolicyCreateWithProperties(
+ kSecPolicyAppleRevocation,
+ revocationOptions as CFDictionary
+ )
+ }
+ }
+}
+
+#endif
diff --git a/Sources/Validator/Default Verifiers/Chain/X509_ChainVerifier.swift b/Sources/Validator/Default Verifiers/Chain/X509_ChainVerifier.swift
new file mode 100644
index 0000000..cd23ec1
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Chain/X509_ChainVerifier.swift
@@ -0,0 +1,318 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+import Crypto
+import SwiftASN1
+@_spi(FixedExpiryValidationTime) import X509
+
+// MARK: - Chain Verifier
+
+public final class X509ChainVerifier: ReceiptChainVerifier {
+ private let store: CertificateStore
+
+ public init(rootCertificates: [Data]) throws {
+ let parsedCertificates = try rootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
+ store = CertificateStore(parsedCertificates)
+ }
+
+ public init(rootCertificates: [Certificate]) {
+ store = CertificateStore(rootCertificates)
+ }
+
+ public func verify(
+ leaf: Certificate,
+ intermediate: [Certificate],
+ policy: [ReceiptChainVerifierPolicy]
+ ) async -> VerificationResult {
+ let result = await verify(
+ leaf: leaf,
+ intermediate: intermediate,
+ rootCertificate: store,
+ policy: policy
+ )
+
+ if case let .couldNotValidate(terminalErrors) = result {
+ for failure in terminalErrors {
+ let failureReasonString = failure.policyFailureReason.description
+ if failureReasonString.contains(Requester.OCSP_NETWORK_REQUEST_FAILED) {
+ return .invalid(ChainVerificationError.revocationCheckFailed)
+ }
+ }
+ return .invalid(ChainVerificationError.chainValidationFailed)
+ }
+
+ return .valid
+ }
+
+ private func verify(
+ leaf: Certificate,
+ intermediate: [Certificate],
+ rootCertificate: CertificateStore,
+ policy: [ReceiptChainVerifierPolicy],
+ ) async -> X509.CertificateValidationResult {
+ var verifier = Verifier(rootCertificates: rootCertificate) {
+ PolicySequence(policy.compactMap { $0.policy })
+ }
+
+ let intermediateStore = CertificateStore(intermediate)
+ return await verifier.validate(leaf: leaf, intermediates: intermediateStore)
+ }
+}
+
+extension X509ChainVerifier: ReceiptVerifier {
+ public func verify(_ receipt: any ReceiptValidatable) async -> VerificationResult {
+ guard let leaf = receipt.leafCertificate else {
+ return .invalid(ChainVerificationError.invalidCertificateData)
+ }
+
+ guard receipt.certificates.count > 1 else {
+ return .invalid(ChainVerificationError.invalidCertificateData)
+ }
+
+ let intermediate = receipt.certificates[1]
+
+ let policy: [ReceiptChainVerifierPolicy]
+ if receipt.environment == .xcode {
+ policy = []
+ } else {
+ policy = [
+ .appleX509Basic,
+ .appStoreReceipt,
+ .validationTime(receipt.validationTime),
+ ]
+ }
+ let result = await verify(
+ leaf: leaf,
+ intermediate: [intermediate],
+ policy: policy
+ )
+
+ return result
+ }
+}
+
+final class AppStoreOIDPolicy: VerifierPolicy {
+ private static let NUMBER_OF_CERTS = 3
+ private static let WWDR_INTERMEDIATE_OID: ASN1ObjectIdentifier = [1, 2, 840, 113_635, 100, 6, 2, 1]
+ private static let RECEIPT_SIGNER_OID: ASN1ObjectIdentifier = [1, 2, 840, 113_635, 100, 6, 11, 1]
+
+ init() {
+ verifyingCriticalExtensions = []
+ }
+
+ var verifyingCriticalExtensions: [SwiftASN1.ASN1ObjectIdentifier]
+
+ func chainMeetsPolicyRequirements(chain: X509.UnverifiedCertificateChain) async -> X509.PolicyEvaluationResult {
+ if chain.count != AppStoreOIDPolicy.NUMBER_OF_CERTS {
+ return X509.PolicyEvaluationResult.failsToMeetPolicy(reason: "Chain has unexpected length")
+ }
+ let intermediateCertificate = chain[1]
+ let leafCertificate = chain[0]
+ if !intermediateCertificate.extensions.contains(where: { ext in
+ ext.oid == AppStoreOIDPolicy.WWDR_INTERMEDIATE_OID
+ }) {
+ return X509.PolicyEvaluationResult.failsToMeetPolicy(
+ reason: "Intermediate certificate does not contain WWDR OID"
+ )
+ }
+ if !leafCertificate.extensions.contains(where: { ext in
+ ext.oid == AppStoreOIDPolicy.RECEIPT_SIGNER_OID
+ }) {
+ return X509.PolicyEvaluationResult.failsToMeetPolicy(
+ reason: "Leaf certificate does not contain Receipt Signing OID"
+ )
+ }
+ return X509.PolicyEvaluationResult.meetsPolicy
+ }
+}
+
+// MARK: - OCSP Requester
+
+#if canImport(Darwin)
+
+final class Requester: OCSPRequester {
+ static let OCSP_NETWORK_REQUEST_FAILED = "OCSP_NETWORK_REQUEST_FAILED"
+
+ private let urlSession: URLSession
+
+ convenience init(sessionConfiguration: URLSessionConfiguration = .default) {
+ self.init(urlSession: URLSession(configuration: sessionConfiguration))
+ }
+
+ init(urlSession: URLSession) {
+ self.urlSession = urlSession
+ }
+
+ init() {
+ self.urlSession = URLSession(configuration: .default)
+ }
+
+ func query(request: [UInt8], uri: String) async -> X509.OCSPRequesterQueryResult {
+ guard let url = URL(string: uri) else {
+ return .terminalError(OCSPValidationError.fetchFailed)
+ }
+
+ var urlRequest = URLRequest(url: url)
+ urlRequest.httpMethod = "POST"
+ urlRequest.httpBody = Data(request)
+ urlRequest.setValue("application/ocsp-request", forHTTPHeaderField: "Content-Type")
+ urlRequest.setValue("application/ocsp-response", forHTTPHeaderField: "Accept")
+ urlRequest.setValue("\(request.count)", forHTTPHeaderField: "Content-Length")
+
+ do {
+ let (data, response) = try await urlSession.data(for: urlRequest)
+
+ guard let httpResponse = response as? HTTPURLResponse else {
+ throw OCSPValidationError.networkError(underlyingError: URLError(.badServerResponse))
+ }
+
+ guard (200...299).contains(httpResponse.statusCode) else {
+ throw OCSPValidationError.httpError(statusCode: UInt(httpResponse.statusCode))
+ }
+
+ return .response([UInt8](data))
+ } catch let error as OCSPValidationError {
+ return .terminalError(error)
+ } catch {
+ return .terminalError(OCSPValidationError.networkError(underlyingError: error))
+ }
+ }
+
+ enum OCSPValidationError: Error, CustomStringConvertible {
+ case fetchFailed
+ case httpError(statusCode: UInt)
+ case networkError(underlyingError: Error)
+
+ var description: String {
+ switch self {
+ case .fetchFailed:
+ return "\(Requester.OCSP_NETWORK_REQUEST_FAILED): Could not read response body"
+ case let .httpError(statusCode):
+ return "\(Requester.OCSP_NETWORK_REQUEST_FAILED): HTTP error \(statusCode)"
+ case let .networkError(underlyingError):
+ return
+ "\(Requester.OCSP_NETWORK_REQUEST_FAILED): Network error - \(underlyingError.localizedDescription)"
+ }
+ }
+ }
+}
+
+#else
+
+import AsyncHTTPClient
+
+final class Requester: OCSPRequester {
+ static let OCSP_NETWORK_REQUEST_FAILED = "OCSP_NETWORK_REQUEST_FAILED"
+
+ private let client: HTTPClient
+
+ init() {
+ self.client = HTTPClient(eventLoopGroupProvider: .singleton)
+ }
+
+ func query(request: [UInt8], uri: String) async -> X509.OCSPRequesterQueryResult {
+ do {
+ var urlRequest = HTTPClientRequest(url: uri)
+ urlRequest.method = .POST
+ urlRequest.headers.add(name: "Content-Type", value: "application/ocsp-request")
+ urlRequest.body = .bytes(request)
+
+ let response = try await client.execute(urlRequest, timeout: .seconds(30))
+
+ guard response.status.code == 200 else {
+ throw OCSPValidationError.httpError(statusCode: UInt(response.status.code))
+ }
+
+ var body = try await response.body.collect(upTo: 1024 * 1024)
+ guard let data = body.readBytes(length: body.readableBytes) else {
+ throw OCSPValidationError.fetchFailed
+ }
+
+ return .response(data)
+ } catch let error as OCSPValidationError {
+ return .terminalError(error)
+ } catch {
+ return .terminalError(OCSPValidationError.networkError(underlyingError: error))
+ }
+ }
+
+ enum OCSPValidationError: Error, CustomStringConvertible {
+ case fetchFailed
+ case httpError(statusCode: UInt)
+ case networkError(underlyingError: Error)
+
+ var description: String {
+ switch self {
+ case .fetchFailed:
+ return "\(Requester.OCSP_NETWORK_REQUEST_FAILED): Could not read response body"
+ case let .httpError(statusCode):
+ return "\(Requester.OCSP_NETWORK_REQUEST_FAILED): HTTP error \(statusCode)"
+ case let .networkError(underlyingError):
+ return
+ "\(Requester.OCSP_NETWORK_REQUEST_FAILED): Network error - \(underlyingError.localizedDescription)"
+ }
+ }
+ }
+}
+
+#endif
+
+extension ReceiptChainVerifierPolicy {
+ fileprivate var policy: VerifierPolicy? {
+ switch self {
+ case .appleX509Basic:
+ return nil
+ case .appStoreReceipt:
+ return AppStoreOIDPolicy()
+ case let .validationTime(time):
+ return RFC5280Policy(fixedExpiryValidationTime: time)
+ case let .onlineValidationTime(time):
+ return OCSPVerifierPolicy(
+ failureMode: .soft,
+ requester: Requester(),
+ fixedExpiryValidationTime: time
+ )
+ }
+ }
+}
+
+@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *)
+public struct PolicySequence: VerifierPolicy {
+ public var policies: [AnyPolicy]
+
+ public init(_ policies: [AnyPolicy]) {
+ self.policies = policies
+ }
+
+ public init(_ policies: [any VerifierPolicy]) {
+ self.policies = policies.map { AnyPolicy($0) }
+ }
+
+ public var verifyingCriticalExtensions: [ASN1ObjectIdentifier] {
+ policies.flatMap { $0.verifyingCriticalExtensions }
+ }
+
+ public mutating func chainMeetsPolicyRequirements(
+ chain: UnverifiedCertificateChain
+ ) async -> PolicyEvaluationResult {
+ for i in policies.indices {
+ switch await policies[i].chainMeetsPolicyRequirements(chain: chain) {
+ case .meetsPolicy:
+ continue
+ case let .failsToMeetPolicy(reason):
+ return .failsToMeetPolicy(reason: reason)
+ }
+ }
+ return .meetsPolicy
+ }
+}
+
+extension PolicyBuilder {
+ @inlinable
+ public static func buildArray(_ components: [VerifierPolicy]) -> some VerifierPolicy {
+ PolicySequence(components.map { AnyPolicy($0) })
+ }
+}
diff --git a/Sources/Validator/Default Verifiers/Hash/DeviceIdentifier.swift b/Sources/Validator/Default Verifiers/Hash/DeviceIdentifier.swift
new file mode 100644
index 0000000..ba7cd96
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Hash/DeviceIdentifier.swift
@@ -0,0 +1,131 @@
+import Foundation
+
+// MARK: - Device Identifier
+
+/// Provides platform-specific device identifiers for receipt hash verification.
+public enum DeviceIdentifier {
+ // Platform-specific implementations below
+}
+
+// MARK: - iOS/tvOS/watchOS Implementation
+
+#if os(iOS) || os(watchOS) || os(tvOS)
+#if canImport(WatchKit)
+import WatchKit
+#elseif canImport(UIKit)
+import UIKit
+#endif
+
+extension DeviceIdentifier {
+ /// Retrieves the vendor identifier for the current device.
+ ///
+ /// - Returns: The device identifier as `Data`, or nil if unavailable.
+ public static var data: Data? {
+ get async {
+ #if canImport(WatchKit)
+ let uuid = WKInterfaceDevice.current().identifierForVendor
+ #elseif canImport(UIKit)
+ let uuid = await UIDevice.current.identifierForVendor
+ #endif
+ return uuid?.data
+ }
+ }
+
+ @_spi(Blocking)
+ public static var data_blocking: Data? {
+ #if canImport(WatchKit)
+ let uuid = WKInterfaceDevice.current().identifierForVendor
+ #elseif canImport(UIKit)
+ let uuid = UIDevice.current.identifierForVendor
+ #endif
+ return uuid?.data
+ }
+}
+#endif
+
+// MARK: - macOS Implementation
+
+#if targetEnvironment(macCatalyst) || os(macOS)
+import IOKit
+
+extension DeviceIdentifier {
+ /// Retrieves the MAC address for the current device.
+ ///
+ /// - Returns: The device identifier as `Data`, or nil if unavailable.
+ public static var data: Data? {
+ obtainMACAddress()
+ }
+
+ @_spi(Blocking)
+ public static var data_blocking: Data? {
+ obtainMACAddress()
+ }
+
+ private static func ioService(named name: String, wantBuiltIn: Bool) -> io_service_t? {
+ let defaultPort: mach_port_t
+
+ if #available(macOS 12.0, macCatalyst 15.0, *) {
+ defaultPort = kIOMainPortDefault
+ } else {
+ defaultPort = 0
+ }
+
+ var iterator = io_iterator_t()
+ defer {
+ if iterator != IO_OBJECT_NULL {
+ IOObjectRelease(iterator)
+ }
+ }
+
+ guard
+ IOServiceGetMatchingServices(
+ defaultPort,
+ IOBSDNameMatching(defaultPort, 0, name),
+ &iterator
+ ) == KERN_SUCCESS
+ else {
+ return nil
+ }
+
+ var candidate = IOIteratorNext(iterator)
+ while candidate != IO_OBJECT_NULL {
+ if let cfBuiltIn = IORegistryEntryCreateCFProperty(
+ candidate,
+ "IOBuiltin" as CFString,
+ kCFAllocatorDefault,
+ 0
+ ) {
+ let isBuiltIn = cfBuiltIn.takeRetainedValue() as? Bool ?? false
+ if isBuiltIn == wantBuiltIn {
+ return candidate
+ }
+ }
+ IOObjectRelease(candidate)
+ candidate = IOIteratorNext(iterator)
+ }
+
+ return nil
+ }
+
+ private static func obtainMACAddress() -> Data? {
+ let interfaces = ["en0", "en1", "en2", "en3", "en4"]
+
+ for interface in interfaces {
+ if let service = ioService(named: interface, wantBuiltIn: true),
+ let cfData = IORegistryEntrySearchCFProperty(
+ service,
+ kIOServicePlane,
+ "IOMACAddress" as CFString,
+ kCFAllocatorDefault,
+ IOOptionBits(kIORegistryIterateRecursively | kIORegistryIterateParents)
+ ) as? Data
+ {
+ IOObjectRelease(service)
+ return cfData
+ }
+ }
+
+ return nil
+ }
+}
+#endif
diff --git a/Sources/Validator/Default Verifiers/Hash/HashVerifier.swift b/Sources/Validator/Default Verifiers/Hash/HashVerifier.swift
new file mode 100644
index 0000000..c56603c
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Hash/HashVerifier.swift
@@ -0,0 +1,59 @@
+import Crypto
+import Foundation
+
+// MARK: - Receipt Hash Verifier
+
+public final class HashVerifier: ReceiptHashVerifier {
+ private let deviceIdentifier: Data
+
+ public init(deviceIdentifier: Data) {
+ self.deviceIdentifier = deviceIdentifier
+ }
+
+ func computeHash(
+ deviceIdentifier: Data,
+ opaqueValue: Data,
+ bundleIdentifierData: Data
+ ) -> Data {
+ var sha1 = Insecure.SHA1()
+ sha1.update(data: deviceIdentifier)
+ sha1.update(data: opaqueValue)
+ sha1.update(data: bundleIdentifierData)
+
+ let digest = sha1.finalize()
+ return Data(digest)
+ }
+
+ public func verify(
+ expectedHash: Data,
+ opaqueValue: Data,
+ bundleIdentifier: Data
+ ) -> VerificationResult {
+ let computedHash = computeHash(
+ deviceIdentifier: deviceIdentifier,
+ opaqueValue: opaqueValue,
+ bundleIdentifierData: bundleIdentifier
+ )
+
+ return computedHash == expectedHash ? .valid : .invalid(HashVerificationError.hashMismatch)
+ }
+}
+
+extension HashVerifier: ReceiptVerifier {
+ public func verify(_ receipt: any ReceiptValidatable) async -> VerificationResult {
+ verify(
+ expectedHash: receipt.receiptHash,
+ opaqueValue: receipt.opaqueValue,
+ bundleIdentifier: receipt.bundleIdentifierData
+ )
+ }
+
+ @_spi(Blocking)
+ public func verify(_ receipt: any ReceiptValidatable) -> VerificationResult {
+ verify(
+ expectedHash: receipt.receiptHash,
+ opaqueValue: receipt.opaqueValue,
+ bundleIdentifier: receipt.bundleIdentifierData
+ )
+ }
+}
diff --git a/Sources/Validator/Default Verifiers/Hash/ReceiptHashVerifier.swift b/Sources/Validator/Default Verifiers/Hash/ReceiptHashVerifier.swift
new file mode 100644
index 0000000..f4e455c
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Hash/ReceiptHashVerifier.swift
@@ -0,0 +1,43 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+/// Errors that occur during receipt hash verification.
+///
+/// Represents failures in validating the receipt hash, which is computed from device, app, and receipt data.
+public enum HashVerificationError: Hashable, Sendable, Error {
+ /// The device identifier is missing and cannot be used for hash computation.
+ case missingDeviceIdentifier
+
+ /// The computed hash does not match the hash stored in the receipt.
+ ///
+ /// This indicates the receipt may have been tampered with or is being validated on a different device.
+ case hashMismatch
+}
+
+/// A protocol for types that verify receipt hashes.
+///
+/// Conforming types compute a hash using device-specific information and verify it matches the hash
+/// stored in the receipt. This ensures the receipt was issued for the specific device and app that is
+/// validating it, preventing misuse of receipts across devices or apps.
+///
+/// - Note: All types conforming to this protocol must be `Sendable`.
+public protocol ReceiptHashVerifier: Sendable {
+ /// Verifies the receipt hash.
+ ///
+ /// Computes a hash using the opaque value, bundle identifier, and the device's unique identifier,
+ /// then compares it to the expected hash from the receipt.
+ ///
+ /// - Parameters:
+ /// - expectedHash: The hash value stored in the receipt to verify against.
+ /// - opaqueValue: The opaque value from the receipt, used in hash computation.
+ /// - bundleIdentifier: The bundle identifier data from the receipt, used in hash computation.
+ /// - Returns: A verification result indicating whether the hash matches or the error that occurred.
+ func verify(
+ expectedHash: Data,
+ opaqueValue: Data,
+ bundleIdentifier: Data
+ ) -> VerificationResult
+}
diff --git a/Sources/Validator/Default Verifiers/Meta/MetaVerifier.swift b/Sources/Validator/Default Verifiers/Meta/MetaVerifier.swift
new file mode 100644
index 0000000..b318a02
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Meta/MetaVerifier.swift
@@ -0,0 +1,86 @@
+public final class MetaVerifier: ReceiptMetaVerifier {
+ public typealias AppVersionProvider = @Sendable () throws -> String
+ public typealias AppBundleIdentifierProvider = @Sendable () throws -> String
+
+ let appVersionProvider: AppVersionProvider
+ let bundleIdentifierProvider: AppBundleIdentifierProvider
+
+ public init(
+ appVersionProvider: @escaping AppVersionProvider,
+ bundleIdentifierProvider: @escaping AppBundleIdentifierProvider
+ ) {
+ self.appVersionProvider = appVersionProvider
+ self.bundleIdentifierProvider = bundleIdentifierProvider
+ }
+
+ /// Creates a MetaVerifier with static expected values.
+ /// Use this on servers when validating receipts from clients.
+ public convenience init(
+ expectedBundleIdentifier: String,
+ expectedAppVersion: String
+ ) {
+ self.init(
+ appVersionProvider: { expectedAppVersion },
+ bundleIdentifierProvider: { expectedBundleIdentifier }
+ )
+ }
+
+ public func verify(
+ bundleIdentifier: String,
+ versionIdentifier: String
+ ) -> VerificationResult {
+ do {
+ let expectedVersion = try appVersionProvider()
+ guard expectedVersion == versionIdentifier else {
+ return .invalid(MetaVerificationError.versionIdentifierMismatch)
+ }
+
+ let expectedBundleIdentifier = try bundleIdentifierProvider()
+ guard expectedBundleIdentifier == bundleIdentifier else {
+ return .invalid(MetaVerificationError.bundleIdentifierMismatch)
+ }
+
+ return .valid
+ } catch {
+ return .invalid(error)
+ }
+ }
+}
+
+extension MetaVerifier: ReceiptVerifier {
+ public func verify(_ receipt: any ReceiptValidatable) -> VerificationResult {
+ return verify(
+ bundleIdentifier: receipt.bundleIdentifier,
+ versionIdentifier: receipt.versionIdentifier
+ )
+ }
+}
+
+#if os(iOS) || os(watchOS) || os(tvOS) || os(macOS) || targetEnvironment(macCatalyst)
+
+import Foundation
+
+// MARK: Native Providers
+
+let nativeAppVersionProvider: MetaVerifier.AppVersionProvider = {
+ #if targetEnvironment(macCatalyst) || os(macOS)
+ let dictKey = "CFBundleShortVersionString"
+ #else
+ let dictKey = "CFBundleVersion"
+ #endif
+
+ guard let v = Bundle.main.infoDictionary?[dictKey] as? String else {
+ throw MetaVerificationError.bundleInfoUnavailable
+ }
+
+ return v
+}
+
+let nativeBundleIdentifierProvider: MetaVerifier.AppBundleIdentifierProvider = {
+ guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
+ throw MetaVerificationError.bundleInfoUnavailable
+ }
+
+ return bundleIdentifier
+}
+#endif
diff --git a/Sources/Validator/Default Verifiers/Meta/ReceiptMetaVerifier.swift b/Sources/Validator/Default Verifiers/Meta/ReceiptMetaVerifier.swift
new file mode 100644
index 0000000..b1c0a77
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Meta/ReceiptMetaVerifier.swift
@@ -0,0 +1,37 @@
+/// Errors that occur during receipt metadata verification.
+///
+/// Represents failures in validating that the receipt belongs to the correct app and version.
+public enum MetaVerificationError: Hashable, Sendable, Error {
+ /// The bundle identifier in the receipt does not match the expected app's bundle identifier.
+ ///
+ /// This can indicate the receipt is being used for a different app than it was issued for.
+ case bundleIdentifierMismatch
+
+ /// The version identifier in the receipt does not match the expected app version.
+ case versionIdentifierMismatch
+
+ /// The app's bundle information could not be retrieved from the main bundle.
+ case bundleInfoUnavailable
+}
+
+/// A protocol for types that verify receipt metadata.
+///
+/// Conforming types validate that the receipt's metadata (bundle identifier and version identifier)
+/// matches the expected values for the app, ensuring the receipt was issued for the correct app
+/// and version.
+///
+/// - Note: All types conforming to this protocol must be `Sendable`.
+public protocol ReceiptMetaVerifier: Sendable {
+ /// Verifies the receipt metadata.
+ ///
+ /// Validates that the receipt's bundle identifier and version identifier match the expected values.
+ ///
+ /// - Parameters:
+ /// - bundleIdentifier: The expected bundle identifier that should be in the receipt.
+ /// - versionIdentifier: The expected version identifier that should be in the receipt.
+ /// - Returns: A verification result indicating whether the metadata is correct or the error that occurred.
+ func verify(
+ bundleIdentifier: String,
+ versionIdentifier: String
+ ) -> VerificationResult
+}
diff --git a/Sources/Validator/Default Verifiers/Signature/ReceiptSignatureVerifier.swift b/Sources/Validator/Default Verifiers/Signature/ReceiptSignatureVerifier.swift
new file mode 100644
index 0000000..a010e69
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Signature/ReceiptSignatureVerifier.swift
@@ -0,0 +1,60 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+import X509
+
+/// The digest algorithm used to hash data before cryptographic signature verification.
+///
+/// This enum specifies which hashing algorithm was used to create the signature over the receipt data.
+public enum DigestAlgorithm: Sendable {
+ /// SHA-1 digest algorithm.
+ ///
+ /// A cryptographic hash function that produces a 160-bit hash value. Included for compatibility
+ /// with older receipt formats.
+ case sha1
+
+ /// SHA-256 digest algorithm.
+ ///
+ /// A cryptographic hash function that produces a 256-bit hash value. Preferred for modern receipts.
+ case sha256
+}
+
+/// Errors that occur during receipt signature verification.
+///
+/// Represents failures in validating the cryptographic signature that authenticates the receipt's integrity.
+public enum SignatureVerificationError: Hashable, Sendable, Error {
+ /// The public key extracted from the certificate is invalid, missing, or cannot be used for verification.
+ case invalidKey
+
+ /// The signature is invalid, malformed, or does not cryptographically verify against the signed data using the provided key.
+ case invalidSignature
+}
+
+/// A protocol for types that verify receipt signatures.
+///
+/// Conforming types implement the cryptographic verification of receipt signatures using a public key.
+/// They validate that the signature was created by the corresponding private key and that the signed data
+/// has not been tampered with.
+///
+/// - Note: All types conforming to this protocol must be `Sendable`.
+public protocol ReceiptSignatureVerifier: Sendable {
+ /// Verifies the receipt signature asynchronously.
+ ///
+ /// Performs cryptographic signature verification using the specified public key and digest algorithm.
+ ///
+ /// - Parameters:
+ /// - key: The public key extracted from the leaf certificate, used to verify the signature.
+ /// - algorithm: The digest algorithm that was used when creating the signature.
+ /// - signedData: The original data that was signed to create the receipt signature.
+ /// - signature: The signature bytes to verify against the signed data.
+ /// - Returns: A verification result indicating whether the signature is valid or the error that occurred.
+ func verify(
+ key: Certificate.PublicKey,
+ algorithm: DigestAlgorithm,
+ signedData: Data,
+ signature: Data
+ ) async -> VerificationResult
+}
diff --git a/Sources/Validator/Default Verifiers/Signature/Sec_SignatureVerifier.swift b/Sources/Validator/Default Verifiers/Signature/Sec_SignatureVerifier.swift
new file mode 100644
index 0000000..cb0dee6
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Signature/Sec_SignatureVerifier.swift
@@ -0,0 +1,90 @@
+#if canImport(Security)
+
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+@preconcurrency import Security
+import SwiftASN1
+import X509
+
+public struct SecSignatureVerifier {
+ public init() {}
+
+ public func verify(
+ key: Data,
+ algorithm: DigestAlgorithm,
+ signedData: Data,
+ signature: Data
+ ) -> VerificationResult {
+ let keyDict: [String: Any] = [
+ kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
+ kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
+ kSecAttrKeySizeInBits as String: 2048,
+ ]
+
+ guard
+ let publicKeySec = SecKeyCreateWithData(
+ key as CFData,
+ keyDict as CFDictionary,
+ nil
+ )
+ else {
+ return .invalid(SignatureVerificationError.invalidKey)
+ }
+
+ var umErrorCF: Unmanaged? = nil
+ guard
+ SecKeyVerifySignature(
+ publicKeySec,
+ algorithm.secKeyAlgorithm,
+ signedData as CFData,
+ signature as CFData,
+ &umErrorCF
+ )
+ else {
+ return .invalid(SignatureVerificationError.invalidSignature)
+ }
+
+ return .valid
+ }
+}
+
+extension SecSignatureVerifier: ReceiptVerifier {
+ public func verify(_ receipt: any ReceiptValidatable) -> VerificationResult {
+ guard let publicKey = receipt.publicKey,
+ let key = try? Data(publicKey.intoBytes())
+ else {
+ return .invalid(SignatureVerificationError.invalidKey)
+ }
+
+ return verify(
+ key: key,
+ algorithm: receipt.digestAlgorithm,
+ signedData: receipt.digestData,
+ signature: receipt.signature
+ )
+ }
+}
+
+extension DigestAlgorithm {
+ fileprivate var secKeyAlgorithm: SecKeyAlgorithm {
+ switch self {
+ case .sha1:
+ return .rsaSignatureMessagePKCS1v15SHA1
+ case .sha256:
+ return .rsaSignatureMessagePKCS1v15SHA256
+ }
+ }
+}
+
+extension DERSerializable {
+ func intoBytes() throws -> Data {
+ var serializer = DER.Serializer()
+ try serializer.serialize(self)
+ return Data(serializer.serializedBytes)
+ }
+}
+#endif
diff --git a/Sources/Validator/Default Verifiers/Signature/X509_SignatureVerifier.swift b/Sources/Validator/Default Verifiers/Signature/X509_SignatureVerifier.swift
new file mode 100644
index 0000000..5c277d8
--- /dev/null
+++ b/Sources/Validator/Default Verifiers/Signature/X509_SignatureVerifier.swift
@@ -0,0 +1,59 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+import _CryptoExtras
+import Crypto
+import SwiftASN1
+import X509
+
+public struct SignatureVerifier: ReceiptSignatureVerifier {
+ public func verify(
+ key: Certificate.PublicKey,
+ algorithm: DigestAlgorithm,
+ signedData: Data,
+ signature: Data
+ ) -> VerificationResult {
+ guard let signatureAlgorithm = algorithm.signatureAlgorithm else {
+ return .invalid(SignatureVerificationError.invalidSignature)
+ }
+
+ let isValid = key.isValidSignature(
+ signature,
+ for: signedData,
+ signatureAlgorithm: signatureAlgorithm
+ )
+
+ return isValid ? .valid : .invalid(SignatureVerificationError.invalidSignature)
+ }
+}
+
+extension SignatureVerifier: ReceiptVerifier {
+ public func verify(_ receipt: any ReceiptValidatable) -> VerificationResult {
+ guard let leaf = receipt.certificates.first else {
+ return .invalid(SignatureVerificationError.invalidKey)
+ }
+
+ return verify(
+ key: leaf.publicKey,
+ algorithm: receipt.digestAlgorithm,
+ signedData: receipt.digestData,
+ signature: receipt.signature
+ )
+ }
+}
+
+extension DigestAlgorithm {
+ fileprivate var signatureAlgorithm: Certificate.SignatureAlgorithm? {
+ switch self {
+ case .sha1:
+ return .sha1WithRSAEncryption
+ case .sha256:
+ return .sha256WithRSAEncryption
+ @unknown default:
+ return nil
+ }
+ }
+}
diff --git a/Sources/Validator/ReceiptValidatable.swift b/Sources/Validator/ReceiptValidatable.swift
new file mode 100644
index 0000000..272fb00
--- /dev/null
+++ b/Sources/Validator/ReceiptValidatable.swift
@@ -0,0 +1,68 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+import X509
+
+// MARK: - ReceiptValidatable
+
+/// A protocol for objects that contain App Store receipt data for validation.
+///
+/// This protocol defines the interface for accessing and validating receipt properties required by verifiers.
+public protocol ReceiptValidatable: Sendable {
+ /// The environment where the receipt was generated (sandbox or production).
+ var environment: InAppReceiptPayload.Environment { get }
+
+ /// The version identifier of the app that generated the receipt.
+ var versionIdentifier: String { get }
+
+ /// The app's bundle identifier as a string.
+ var bundleIdentifier: String { get }
+
+ /// The app's bundle identifier encoded as data.
+ var bundleIdentifierData: Data { get }
+
+ /// The certificate chain used to sign the receipt, parsed as `Certificate` objects.
+ var certificates: [Certificate] { get }
+
+ /// The raw certificate data from the receipt.
+ var certificatesRaw: [Data] { get }
+
+ /// The expected hash value computed from the receipt's opaque value, bundle identifier, and device identifier.
+ var receiptHash: Data { get }
+
+ /// The opaque value from the receipt used in hash computation.
+ var opaqueValue: Data { get }
+
+ /// The data that was signed to create the receipt signature.
+ var digestData: Data { get }
+
+ /// The digest algorithm used to hash the signed data.
+ var digestAlgorithm: DigestAlgorithm { get }
+
+ /// The signature that verifies the authenticity of the receipt.
+ var signature: Data { get }
+
+ /// The date to use for validating certificate expiration and other time-dependent verification checks.
+ var validationTime: Date { get }
+}
+
+extension ReceiptValidatable {
+ var leafCertificate: Certificate? {
+ certificates.first
+ }
+
+ var intermediateCertificates: [Certificate] {
+ guard certificates.count > 1 else {
+ return []
+ }
+
+ return [certificates[1]]
+ }
+
+ var publicKey: Certificate.PublicKey? {
+ leafCertificate?.publicKey
+ }
+}
diff --git a/Sources/Validator/ReceiptVerifier.swift b/Sources/Validator/ReceiptVerifier.swift
new file mode 100644
index 0000000..cada721
--- /dev/null
+++ b/Sources/Validator/ReceiptVerifier.swift
@@ -0,0 +1,61 @@
+/// A protocol for types that verify aspects of an App Store receipt.
+///
+/// Conforming types perform specific validation checks on receipts, such as certificate chain validation,
+/// signature verification, hash verification, or metadata validation. Multiple verifiers can be composed
+/// together to perform comprehensive receipt validation.
+public protocol ReceiptVerifier: Sendable {
+ /// Verifies the receipt asynchronously.
+ ///
+ /// - Parameter receipt: The receipt data to verify.
+ /// - Returns: A verification result indicating success or failure with an associated error.
+ func verify(_ receipt: any ReceiptValidatable) async -> VerificationResult
+
+ /// Verifies the receipt synchronously.
+ ///
+ /// This is a blocking variant of the async `verify(_:)` method for use in synchronous contexts.
+ ///
+ /// - Parameter receipt: The receipt data to verify.
+ /// - Returns: A verification result indicating success or failure with an associated error.
+ @_spi(Blocking)
+ func verify(_ receipt: any ReceiptValidatable) -> VerificationResult
+}
+
+/// The result of a verification operation.
+///
+/// Represents the outcome of a receipt verification check. Use the `isValid` or `isInvalid` properties
+/// to determine the verification status.
+public enum VerificationResult: Sendable {
+ /// The verification succeeded.
+ case valid
+
+ /// The verification failed with an error.
+ ///
+ /// - Parameter error: The error that caused the verification to fail.
+ case invalid(any Error)
+
+ /// A Boolean value indicating whether the verification succeeded.
+ ///
+ /// - Returns: `true` if verification succeeded, `false` otherwise.
+ @inlinable
+ public var isValid: Bool {
+ switch self {
+ case .valid:
+ return true
+ case .invalid:
+ return false
+ }
+ }
+
+ /// A Boolean value indicating whether the verification failed.
+ ///
+ /// - Returns: `true` if verification failed, `false` otherwise.
+ @inlinable
+ public var isInvalid: Bool {
+ switch self {
+ case .valid:
+ return false
+ case .invalid:
+ return true
+ }
+ }
+}
diff --git a/Sources/Validator/Validator.swift b/Sources/Validator/Validator.swift
new file mode 100644
index 0000000..f4f65bd
--- /dev/null
+++ b/Sources/Validator/Validator.swift
@@ -0,0 +1,106 @@
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+import X509
+
+/// Errors that can occur during receipt validation.
+///
+/// This error type represents various failures that can occur when validating App Store receipts.
+public enum ReceiptValidatorError: Error, Sendable {
+ /// The root certificate is invalid or cannot be decoded.
+ case rootCertificateInvalid((any Error)?)
+
+ /// The device identifier could not be retrieved.
+ case deviceIdentifierIsNotFound
+
+ /// The receipt structure is invalid or missing required fields.
+ case invalidReceiptStructure
+}
+
+// MARK: - ReceiptValidator
+
+/// Validates App Store receipts using a composition of verifiers.
+///
+/// `ReceiptValidator` runs multiple verification checks on a receipt in parallel using task groups.
+/// It composes multiple `ReceiptVerifier` instances, each responsible for a specific verification aspect.
+/// If any verifier fails, validation immediately returns that failure without running remaining verifiers.
+///
+/// Use the `@VerifierBuilder` result builder to compose verifiers:
+///
+/// ```swift
+/// let validator = ReceiptValidator {
+/// X509ChainVerifier(rootCertificates: [rootCert])
+/// SignatureVerifier()
+/// HashVerifier()
+/// }
+/// ```
+public struct ReceiptValidator: Sendable {
+ let verifiers: [any ReceiptVerifier]
+
+ /// Creates a receipt validator with the specified verifiers.
+ ///
+ /// - Parameter policies: A builder closure that returns an array of verifiers to run.
+ /// Use the `@VerifierBuilder` syntax to compose verifiers.
+ public init(@VerifierBuilder policies: () -> [any ReceiptVerifier]) {
+ verifiers = policies()
+ }
+
+ /// Validates a receipt asynchronously using all configured verifiers.
+ ///
+ /// The validation runs verifiers in parallel using task groups. If any verifier fails,
+ /// remaining verifiers are cancelled and the failure is immediately returned.
+ ///
+ /// - Parameter receipt: The receipt to validate.
+ /// - Returns: A verification result indicating whether all verifications passed or the first failure.
+ public func validate(_ receipt: any ReceiptValidatable) async -> VerificationResult {
+ await withTaskGroup(of: VerificationResult.self) { group in
+ for verifier in verifiers {
+ group.addTask {
+ await verifier.verify(receipt)
+ }
+ }
+
+ for await result in group {
+ if case let .invalid(error) = result {
+ group.cancelAll()
+ return .invalid(error)
+ }
+ }
+
+ return .valid
+ }
+ }
+}
+
+extension ReceiptValidator {
+ /// Creates a default validator with all standard verification checks.
+ ///
+ /// This factory method creates a fully configured receipt validator that performs:
+ /// - X.509 certificate chain verification against the provided root certificate
+ /// - Cryptographic signature verification
+ /// - Receipt hash verification
+ /// - Metadata verification (bundle identifier and app version)
+ ///
+ /// - Parameters:
+ /// - rootCertificate: The Apple root certificate in DER-encoded format.
+ /// - deviceIdentifier: The device identifier data for hash verification.
+ /// - Returns: A configured `ReceiptValidator` instance.
+ /// - Throws: An error if the root certificate cannot be decoded.
+ public static func `default`(
+ rootCertificate: Certificate,
+ deviceIdentifier: Data
+ ) -> ReceiptValidator {
+ return ReceiptValidator {
+ X509ChainVerifier(rootCertificates: [rootCertificate])
+ SignatureVerifier()
+ HashVerifier(deviceIdentifier: deviceIdentifier)
+ MetaVerifier(
+ appVersionProvider: nativeAppVersionProvider,
+ bundleIdentifierProvider: nativeBundleIdentifierProvider
+ )
+ }
+ }
+}
diff --git a/Sources/Validator/VerifierBuilder.swift b/Sources/Validator/VerifierBuilder.swift
new file mode 100644
index 0000000..73b66ba
--- /dev/null
+++ b/Sources/Validator/VerifierBuilder.swift
@@ -0,0 +1,72 @@
+// MARK: - VerifierBuilder
+
+/// A result builder for composing receipt verifiers.
+///
+/// `VerifierBuilder` is a DSL (Domain Specific Language) that enables composition
+/// of `ReceiptVerifier` instances.
+///
+/// Use `@VerifierBuilder` syntax when initializing a `ReceiptValidator`:
+///
+/// ```swift
+/// let validator = ReceiptValidator {
+/// X509ChainVerifier(rootCertificates: [rootCert])
+/// SignatureVerifier()
+/// if shouldVerifyHash {
+/// HashVerifier()
+/// }
+/// }
+/// ```
+@resultBuilder
+public struct VerifierBuilder: Sendable {}
+
+extension VerifierBuilder {
+ public static func buildExpression(_ expression: any ReceiptVerifier) -> [any ReceiptVerifier] {
+ [expression]
+ }
+
+ public static func buildExpression(_ expression: [any ReceiptVerifier]) -> [any ReceiptVerifier] {
+ expression
+ }
+
+ public static func buildBlock() -> [any ReceiptVerifier] {
+ []
+ }
+
+ public static func buildPartialBlock(first policy: any ReceiptVerifier) -> [any ReceiptVerifier] {
+ [policy]
+ }
+
+ public static func buildPartialBlock(first policy: [any ReceiptVerifier]) -> [any ReceiptVerifier] {
+ policy
+ }
+
+ public static func buildPartialBlock(
+ accumulated policies: [any ReceiptVerifier],
+ next policy: any ReceiptVerifier
+ ) -> [any ReceiptVerifier] {
+ policies + [policy]
+ }
+
+ public static func buildPartialBlock(
+ accumulated policies: [any ReceiptVerifier],
+ next policy: [any ReceiptVerifier]
+ ) -> [any ReceiptVerifier] {
+ policies + policy
+ }
+
+ public static func buildOptional(_ policy: [any ReceiptVerifier]?) -> [any ReceiptVerifier] {
+ policy ?? []
+ }
+
+ public static func buildEither(first policy: [any ReceiptVerifier]) -> [any ReceiptVerifier] {
+ policy
+ }
+
+ public static func buildEither(second policy: [any ReceiptVerifier]) -> [any ReceiptVerifier] {
+ policy
+ }
+
+ public static func buildArray(_ policies: [[any ReceiptVerifier]]) -> [any ReceiptVerifier] {
+ policies.flatMap { $0 }
+ }
+}
diff --git a/TPInAppReceipt.podspec b/TPInAppReceipt.podspec
deleted file mode 100644
index d556255..0000000
--- a/TPInAppReceipt.podspec
+++ /dev/null
@@ -1,38 +0,0 @@
-Pod::Spec.new do |s|
-
- s.name = "TPInAppReceipt"
- s.version = "3.4.2"
- 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 = { "tikhop" => "hi@tikhop.com" }
-
- s.swift_versions = ['5.3']
- 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.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.7'
- end
-
- s.subspec 'Objc' do |objc|
- objc.source_files = "Sources/Objc/*.{swift}"
- objc.dependency 'TPInAppReceipt/Core'
- end
-
- s.resource_bundles = { "TPInAppReceipt" => "Source/PrivacyInfo.xcprivacy" }
- s.default_subspecs = 'Core'
-
-end
diff --git a/Tests/AppReceiptTests.swift b/Tests/AppReceiptTests.swift
new file mode 100644
index 0000000..e00b94e
--- /dev/null
+++ b/Tests/AppReceiptTests.swift
@@ -0,0 +1,226 @@
+import Foundation
+import Testing
+import X509
+
+@testable import TPInAppReceipt
+
+@Suite("AppReceipt")
+struct AppReceiptTests {
+ let knownDeviceUUID = UUID(uuidString: "956328D9-CC6A-47F4-BE40-6953FB0AB6C7")!
+
+ // MARK: - Decoding & Metadata
+
+ @Test
+ func decodedReceiptExposesEnvironment() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ #expect(receipt.environment == .productionSandbox)
+ }
+
+ @Test
+ func decodedReceiptExposesBundleIdentifier() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ #expect(!receipt.bundleIdentifier.isEmpty)
+ }
+
+ @Test
+ func decodedReceiptExposesAppVersion() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ #expect(!receipt.appVersion.isEmpty)
+ }
+
+ @Test
+ func creationDateIsReasonable() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let year2010 = Date(timeIntervalSince1970: 1_262_304_000)
+ #expect(receipt.creationDate > year2010)
+ }
+
+ @Test
+ func payloadRawDataIsNonEmpty() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ #expect(!receipt.payloadRawData.isEmpty)
+ }
+
+ @Test
+ func hasValidStructure() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ #expect(receipt.hasValidStructure)
+ }
+
+ @Test
+ func productionReceiptExposesCorrectEnvironment() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-production")
+ #expect(receipt.environment == .production)
+ }
+
+ @Test
+ func xcodeReceiptExposesCorrectEnvironment() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-no-orig-purchase-date")
+ #expect(receipt.environment == .xcode)
+ }
+
+ // MARK: - Validation
+
+ @Test
+ func asyncValidationPassesWithCorrectCertAndDevice() async throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let rootCertData = TestingUtility.loadRootCertificate()
+ let rootCert = try Certificate(derEncoded: rootCertData.bytes)
+
+ let validator = ReceiptValidator {
+ X509ChainVerifier(rootCertificates: [rootCert])
+ SignatureVerifier()
+ HashVerifier(deviceIdentifier: knownDeviceUUID.data)
+ }
+
+ let result = await validator.validate(receipt)
+ #expect(result.isValid)
+ }
+
+ @Test
+ func asyncValidationFailsWithWrongDevice() async throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let rootCertData = TestingUtility.loadRootCertificate()
+ let rootCert = try Certificate(derEncoded: rootCertData.bytes)
+ let wrongDevice = Data(repeating: 0xFF, count: 16)
+
+ let validator = ReceiptValidator {
+ X509ChainVerifier(rootCertificates: [rootCert])
+ SignatureVerifier()
+ HashVerifier(deviceIdentifier: wrongDevice)
+ }
+
+ let result = await validator.validate(receipt)
+ #expect(result.isInvalid)
+ }
+
+ @Test
+ func asyncValidationFailsWithWrongRootCertificate() async throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let wrongCertData = TestingUtility.loadXcodeRootCertificate()
+ let wrongCert = try Certificate(derEncoded: wrongCertData.bytes)
+
+ let validator = ReceiptValidator {
+ X509ChainVerifier(rootCertificates: [wrongCert])
+ SignatureVerifier()
+ HashVerifier(deviceIdentifier: knownDeviceUUID.data)
+ }
+
+ let result = await validator.validate(receipt)
+ #expect(result.isInvalid)
+ }
+
+ // MARK: - Purchase Queries (negative cases)
+
+ @Test
+ func containsPurchaseReturnsFalseForUnknownProduct() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ #expect(!receipt.containsPurchase(ofProductIdentifier: "com.nonexistent.product.xyz"))
+ }
+
+ @Test
+ func purchasesReturnsEmptyForUnknownProduct() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ #expect(receipt.purchases(ofProductIdentifier: "com.nonexistent.product.xyz").isEmpty)
+ }
+
+ @Test
+ func originalTransactionIdentifierReturnsNilForUnknownProduct() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ #expect(receipt.originalTransactionIdentifier(ofProductIdentifier: "com.nonexistent.product.xyz") == nil)
+ }
+
+ @Test
+ func noActiveSubscriptionForUnknownProduct() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ #expect(
+ !receipt.hasActiveAutoRenewableSubscription(
+ ofProductIdentifier: "com.nonexistent.product.xyz",
+ forDate: Date()
+ )
+ )
+ }
+
+ @Test
+ func lastAutoRenewableReturnsNilForUnknownProduct() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ #expect(
+ receipt.lastAutoRenewableSubscriptionPurchase(
+ ofProductIdentifier: "com.nonexistent.product.xyz"
+ ) == nil
+ )
+ }
+
+ // MARK: - Purchase Queries (positive cases via receipt-with-transaction)
+
+ @Test
+ func containsPurchaseReturnsTrueForExistingProduct() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-with-transaction")
+ try #require(!receipt.purchases.isEmpty, "Receipt must contain purchases for this test")
+
+ let productId = receipt.purchases[0].productIdentifier
+ #expect(receipt.containsPurchase(ofProductIdentifier: productId))
+ }
+
+ @Test
+ func purchasesFiltersToMatchingProduct() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-with-transaction")
+ try #require(!receipt.purchases.isEmpty, "Receipt must contain purchases for this test")
+
+ let productId = receipt.purchases[0].productIdentifier
+ let filtered = receipt.purchases(ofProductIdentifier: productId)
+
+ #expect(!filtered.isEmpty)
+ #expect(filtered.allSatisfy { $0.productIdentifier == productId })
+ }
+
+ @Test
+ func purchasesSortedByDateDescendingByDefault() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-with-transaction")
+ try #require(!receipt.purchases.isEmpty, "Receipt must contain purchases for this test")
+
+ let productId = receipt.purchases[0].productIdentifier
+ let sorted = receipt.purchases(ofProductIdentifier: productId)
+
+ for i in 0..<(sorted.count - 1) {
+ #expect(sorted[i].purchaseDate >= sorted[i + 1].purchaseDate)
+ }
+ }
+
+ @Test
+ func purchasesWithCustomSort() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-with-transaction")
+ try #require(!receipt.purchases.isEmpty, "Receipt must contain purchases for this test")
+
+ let productId = receipt.purchases[0].productIdentifier
+ let ascending = receipt.purchases(ofProductIdentifier: productId) {
+ $0.purchaseDate < $1.purchaseDate
+ }
+
+ for i in 0..<(ascending.count - 1) {
+ #expect(ascending[i].purchaseDate <= ascending[i + 1].purchaseDate)
+ }
+ }
+
+ @Test
+ func originalTransactionIdentifierReturnsValueForExistingProduct() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-with-transaction")
+ try #require(!receipt.purchases.isEmpty, "Receipt must contain purchases for this test")
+
+ let productId = receipt.purchases[0].productIdentifier
+ #expect(receipt.originalTransactionIdentifier(ofProductIdentifier: productId) != nil)
+ }
+
+ @Test
+ func hasPurchasesReflectsPurchasePresence() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-with-transaction")
+ #expect(receipt.hasPurchases)
+ }
+
+ @Test
+ func autoRenewablePurchasesFiltersSubscriptions() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-with-transaction")
+ let autoRenewable = receipt.autoRenewablePurchases
+ #expect(autoRenewable.allSatisfy { $0.isRenewableSubscription })
+ }
+}
diff --git a/Tests/Assets/AppleIncRootCertificate.cer b/Tests/Assets/AppleIncRootCertificate.cer
new file mode 100644
index 0000000..8a9ff24
Binary files /dev/null and b/Tests/Assets/AppleIncRootCertificate.cer differ
diff --git a/Tests/Assets/StoreKitTestCertificate.cer b/Tests/Assets/StoreKitTestCertificate.cer
new file mode 100644
index 0000000..5896156
Binary files /dev/null and b/Tests/Assets/StoreKitTestCertificate.cer differ
diff --git a/Tests/Assets/receipt-crash b/Tests/Assets/receipt-crash
new file mode 100644
index 0000000..d9a5e95
--- /dev/null
+++ b/Tests/Assets/receipt-crash
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/Tests/Assets/receipt-from-known-device b/Tests/Assets/receipt-from-known-device
new file mode 100644
index 0000000..4d0f2e9
--- /dev/null
+++ b/Tests/Assets/receipt-from-known-device
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/Tests/Assets/receipt-legacy b/Tests/Assets/receipt-legacy
new file mode 100644
index 0000000..8a4bc5f
--- /dev/null
+++ b/Tests/Assets/receipt-legacy
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/Tests/Assets/receipt-new b/Tests/Assets/receipt-new
new file mode 100644
index 0000000..46fbd6b
--- /dev/null
+++ b/Tests/Assets/receipt-new
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/Tests/Assets/receipt-no-orig-purchase-date b/Tests/Assets/receipt-no-orig-purchase-date
new file mode 100644
index 0000000..eede83b
--- /dev/null
+++ b/Tests/Assets/receipt-no-orig-purchase-date
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/Tests/Assets/receipt-production b/Tests/Assets/receipt-production
new file mode 100644
index 0000000..9f90214
--- /dev/null
+++ b/Tests/Assets/receipt-production
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/Tests/Assets/receipt-watch b/Tests/Assets/receipt-watch
new file mode 100644
index 0000000..0f78688
--- /dev/null
+++ b/Tests/Assets/receipt-watch
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/Tests/Assets/receipt-with-transaction b/Tests/Assets/receipt-with-transaction
new file mode 100644
index 0000000..6b2d6e7
--- /dev/null
+++ b/Tests/Assets/receipt-with-transaction
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/Tests/Blocking/AppReceiptBlockingTests.swift b/Tests/Blocking/AppReceiptBlockingTests.swift
new file mode 100644
index 0000000..f0be7bb
--- /dev/null
+++ b/Tests/Blocking/AppReceiptBlockingTests.swift
@@ -0,0 +1,25 @@
+import Foundation
+import Testing
+
+@_spi(Blocking) @testable import TPInAppReceipt
+
+@Suite("AppReceipt Blocking Mode")
+struct AppReceiptBlockingTests {
+ let knownDeviceUUID = UUID(uuidString: "956328D9-CC6A-47F4-BE40-6953FB0AB6C7")!
+
+ @Test
+ func validReceiptPassesChainSignatureAndHashVerification() throws {
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let rootCertificate = TestingUtility.loadRootCertificate()
+
+ let validator = ReceiptValidator {
+ SecChainVerifier(rootCertificates: [rootCertificate])
+ SecSignatureVerifier()
+ HashVerifier(deviceIdentifier: knownDeviceUUID.data)
+ }
+
+ let result = validator.validate_blocking(receipt)
+
+ #expect(result.isValid)
+ }
+}
diff --git a/Tests/Core/InAppPurchaseTests.swift b/Tests/Core/InAppPurchaseTests.swift
new file mode 100644
index 0000000..e3aaaae
--- /dev/null
+++ b/Tests/Core/InAppPurchaseTests.swift
@@ -0,0 +1,116 @@
+import Foundation
+import Testing
+
+@testable import TPInAppReceipt
+
+@Suite("InAppPurchase")
+struct InAppPurchaseTests {
+ // MARK: - isRenewableSubscription
+
+ @Test
+ func renewableWhenExpirationDatePresent() {
+ let purchase = makePurchase(
+ expirationDate: Date().addingTimeInterval(3600),
+ cancellationDate: nil
+ )
+ #expect(purchase.isRenewableSubscription)
+ }
+
+ @Test
+ func notRenewableWhenExpirationDateNil() {
+ let purchase = makePurchase(expirationDate: nil, cancellationDate: nil)
+ #expect(!purchase.isRenewableSubscription)
+ }
+
+ // MARK: - isActiveAutoRenewableSubscription
+
+ @Test
+ func activeWhenDateWithinRange() {
+ let now = Date()
+ let purchase = makePurchase(
+ purchaseDate: now.addingTimeInterval(-3600),
+ expirationDate: now.addingTimeInterval(3600),
+ cancellationDate: nil
+ )
+ #expect(purchase.isActiveAutoRenewableSubscription(forDate: now))
+ }
+
+ @Test
+ func inactiveWhenExpired() {
+ let now = Date()
+ let purchase = makePurchase(
+ purchaseDate: now.addingTimeInterval(-7200),
+ expirationDate: now.addingTimeInterval(-3600),
+ cancellationDate: nil
+ )
+ #expect(!purchase.isActiveAutoRenewableSubscription(forDate: now))
+ }
+
+ @Test
+ func inactiveWhenCancelled() {
+ let now = Date()
+ let purchase = makePurchase(
+ purchaseDate: now.addingTimeInterval(-3600),
+ expirationDate: now.addingTimeInterval(3600),
+ cancellationDate: now.addingTimeInterval(-1800)
+ )
+ #expect(!purchase.isActiveAutoRenewableSubscription(forDate: now))
+ }
+
+ @Test
+ func inactiveBeforePurchaseDate() {
+ let now = Date()
+ let purchase = makePurchase(
+ purchaseDate: now.addingTimeInterval(3600),
+ expirationDate: now.addingTimeInterval(7200),
+ cancellationDate: nil
+ )
+ #expect(!purchase.isActiveAutoRenewableSubscription(forDate: now))
+ }
+
+ @Test
+ func activeAtExactPurchaseDate() {
+ let now = Date()
+ let purchase = makePurchase(
+ purchaseDate: now,
+ expirationDate: now.addingTimeInterval(3600),
+ cancellationDate: nil
+ )
+ #expect(purchase.isActiveAutoRenewableSubscription(forDate: now))
+ }
+
+ @Test
+ func inactiveAtExactExpirationDate() {
+ let now = Date()
+ let purchase = makePurchase(
+ purchaseDate: now.addingTimeInterval(-3600),
+ expirationDate: now,
+ cancellationDate: nil
+ )
+ #expect(!purchase.isActiveAutoRenewableSubscription(forDate: now))
+ }
+}
+
+// MARK: - Helpers
+
+private func makePurchase(
+ purchaseDate: Date = Date(),
+ expirationDate: Date?,
+ cancellationDate: Date?
+) -> InAppPurchase {
+ InAppPurchase(
+ productIdentifier: "com.example.subscription",
+ productType: .autoRenewableSubscription,
+ transactionIdentifier: UUID().uuidString,
+ originalTransactionIdentifier: UUID().uuidString,
+ purchaseDate: purchaseDate,
+ originalPurchaseDate: purchaseDate,
+ subscriptionExpirationDate: expirationDate,
+ cancellationDate: cancellationDate,
+ subscriptionTrialPeriod: false,
+ subscriptionIntroductoryPricePeriod: false,
+ webOrderLineItemID: nil,
+ promotionalOfferIdentifier: nil,
+ quantity: 1
+ )
+}
diff --git a/Tests/Decoding/AppReceiptDecoderTests.swift b/Tests/Decoding/AppReceiptDecoderTests.swift
new file mode 100644
index 0000000..053f67a
--- /dev/null
+++ b/Tests/Decoding/AppReceiptDecoderTests.swift
@@ -0,0 +1,69 @@
+import Foundation
+import SwiftASN1
+import Testing
+
+@testable import TPInAppReceipt
+
+@Suite("AppReceiptDecoder")
+struct AppReceiptDecoderTests {
+ @Test
+ func defaultDecoderDecodesReceipt() throws {
+ let receiptData = TestingUtility.readReceipt("Assets/receipt-from-known-device")
+ let decoder = AppReceiptDecoder.default
+
+ let receipt = try decoder.decode(from: receiptData)
+
+ #expect(receipt.hasValidStructure)
+ #expect(!receipt.bundleIdentifier.isEmpty)
+ }
+
+ @Test
+ func staticReceiptFactoryMethod() throws {
+ let receiptData = TestingUtility.readReceipt("Assets/receipt-from-known-device")
+
+ let receipt = try AppReceipt.receipt(from: receiptData)
+
+ #expect(receipt.hasValidStructure)
+ #expect(!receipt.bundleIdentifier.isEmpty)
+ }
+
+ @Test
+ func decodeThrowsOnInvalidData() throws {
+ let invalidData = Data("invalid receipt data".utf8)
+ let decoder = AppReceiptDecoder.default
+
+ #expect(throws: Error.self) {
+ try decoder.decode(from: invalidData)
+ }
+ }
+
+ @Test
+ func decodeThrowsOnEmptyData() throws {
+ let emptyData = Data()
+ let decoder = AppReceiptDecoder.default
+
+ #expect(throws: Error.self) {
+ try decoder.decode(from: emptyData)
+ }
+ }
+
+ @Test
+ func customEngineIsCalled() throws {
+ final class MockEngine: AppReceiptDecoder.Engine {
+ nonisolated(unsafe) var decodeCalled = false
+
+ func decode(from data: Data) throws -> AppReceipt {
+ decodeCalled = true
+ return try AppReceipt(berEncoded: BER.parse(data.bytes))
+ }
+ }
+
+ let mockEngine = MockEngine()
+ let decoder = AppReceiptDecoder(engine: mockEngine)
+ let receiptData = TestingUtility.readReceipt("Assets/receipt-from-known-device")
+
+ _ = try decoder.decode(from: receiptData)
+
+ #expect(mockEngine.decodeCalled)
+ }
+}
diff --git a/Tests/Decoding/SwiftASN1DecoderTests.swift b/Tests/Decoding/SwiftASN1DecoderTests.swift
new file mode 100644
index 0000000..bb2c379
--- /dev/null
+++ b/Tests/Decoding/SwiftASN1DecoderTests.swift
@@ -0,0 +1,49 @@
+import Foundation
+import SwiftASN1
+import Testing
+
+@testable import TPInAppReceipt
+
+@Suite("SwiftASN1ReceiptDecoder")
+struct SwiftASN1DecoderTests {
+ struct ReceiptTestCase: Sendable, CustomTestStringConvertible {
+ let path: String
+ let expectedEnvironment: InAppReceiptPayload.Environment?
+
+ var testDescription: String { path }
+ }
+
+ static let allReceipts: [ReceiptTestCase] = [
+ .init(path: "Assets/receipt-from-known-device", expectedEnvironment: .productionSandbox),
+ .init(path: "Assets/receipt-production", expectedEnvironment: .production),
+ .init(path: "Assets/receipt-no-orig-purchase-date", expectedEnvironment: .xcode),
+ .init(path: "Assets/receipt-crash", expectedEnvironment: nil),
+ .init(path: "Assets/receipt-legacy", expectedEnvironment: nil),
+ .init(path: "Assets/receipt-new", expectedEnvironment: nil),
+ .init(path: "Assets/receipt-watch", expectedEnvironment: nil),
+ .init(path: "Assets/receipt-with-transaction", expectedEnvironment: nil),
+ ]
+
+ @Test(arguments: allReceipts)
+ func decodeReceipt(testCase: ReceiptTestCase) throws {
+ let receiptData = TestingUtility.readReceipt(testCase.path)
+ let decoder = SwiftASN1ReceiptDecoder()
+
+ let receipt = try decoder.decode(from: receiptData)
+
+ #expect(receipt.hasValidStructure)
+ if let expected = testCase.expectedEnvironment {
+ #expect(receipt.environment == expected)
+ }
+ }
+
+ @Test
+ func decodeThrowsOnMalformedData() throws {
+ let malformedData = Data([0x01, 0x02, 0x03, 0x04])
+ let decoder = SwiftASN1ReceiptDecoder()
+
+ #expect(throws: Error.self) {
+ try decoder.decode(from: malformedData)
+ }
+ }
+}
diff --git a/Tests/Extras/IntroOfferEligibilityTests.swift b/Tests/Extras/IntroOfferEligibilityTests.swift
new file mode 100644
index 0000000..cefaf38
--- /dev/null
+++ b/Tests/Extras/IntroOfferEligibilityTests.swift
@@ -0,0 +1,178 @@
+import Foundation
+import Testing
+
+@testable import TPInAppReceipt
+
+@Suite("IntroOfferEligibility")
+struct IntroOfferEligibilityTests {
+ // MARK: - Per-Product Eligibility
+
+ @Test
+ func eligibleWhenNoPurchases() {
+ let payload = makePayload(purchases: [])
+ #expect(payload.isEligibleForIntroOffer(for: "com.example.monthly"))
+ }
+
+ @Test
+ func eligibleWhenProductNotPurchased() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.yearly", trial: false, intro: false)
+ ])
+ #expect(payload.isEligibleForIntroOffer(for: "com.example.monthly"))
+ }
+
+ @Test
+ func eligibleWhenPurchasedWithoutTrialOrIntro() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", trial: false, intro: false)
+ ])
+ #expect(payload.isEligibleForIntroOffer(for: "com.example.monthly"))
+ }
+
+ @Test
+ func notEligibleWhenRedeemedTrial() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", trial: true, intro: false)
+ ])
+ #expect(!payload.isEligibleForIntroOffer(for: "com.example.monthly"))
+ }
+
+ @Test
+ func notEligibleWhenRedeemedIntroPrice() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", trial: false, intro: true)
+ ])
+ #expect(!payload.isEligibleForIntroOffer(for: "com.example.monthly"))
+ }
+
+ @Test
+ func notEligibleWhenRedeemedBothTrialAndIntro() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", trial: true, intro: true)
+ ])
+ #expect(!payload.isEligibleForIntroOffer(for: "com.example.monthly"))
+ }
+
+ @Test
+ func eligibleWhenOtherProductUsedTrial() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.yearly", trial: true, intro: false)
+ ])
+ #expect(payload.isEligibleForIntroOffer(for: "com.example.monthly"))
+ }
+
+ @Test
+ func notEligibleWhenAnyPurchaseForProductUsedTrial() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", trial: false, intro: false),
+ makePurchase(productIdentifier: "com.example.monthly", trial: true, intro: false),
+ ])
+ #expect(!payload.isEligibleForIntroOffer(for: "com.example.monthly"))
+ }
+
+ // MARK: - Per-Group Eligibility
+
+ @Test
+ func groupEligibleWhenNoPurchases() {
+ let payload = makePayload(purchases: [])
+ let group: Set = ["com.example.monthly", "com.example.yearly"]
+ #expect(payload.isEligibleForIntroOffer(for: group))
+ }
+
+ @Test
+ func groupEligibleWhenNoGroupProductPurchased() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.other", trial: true, intro: false)
+ ])
+ let group: Set = ["com.example.monthly", "com.example.yearly"]
+ #expect(payload.isEligibleForIntroOffer(for: group))
+ }
+
+ @Test
+ func groupEligibleWhenGroupProductPurchasedWithoutTrialOrIntro() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", trial: false, intro: false)
+ ])
+ let group: Set = ["com.example.monthly", "com.example.yearly"]
+ #expect(payload.isEligibleForIntroOffer(for: group))
+ }
+
+ @Test
+ func groupNotEligibleWhenAnyGroupProductUsedTrial() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", trial: true, intro: false)
+ ])
+ let group: Set = ["com.example.monthly", "com.example.yearly"]
+ #expect(!payload.isEligibleForIntroOffer(for: group))
+ }
+
+ @Test
+ func groupNotEligibleWhenAnyGroupProductUsedIntroPrice() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.yearly", trial: false, intro: true)
+ ])
+ let group: Set = ["com.example.monthly", "com.example.yearly"]
+ #expect(!payload.isEligibleForIntroOffer(for: group))
+ }
+
+ @Test
+ func groupNotEligibleWhenOneOfManyGroupProductsUsedTrial() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", trial: false, intro: false),
+ makePurchase(productIdentifier: "com.example.yearly", trial: true, intro: false),
+ ])
+ let group: Set = ["com.example.monthly", "com.example.yearly"]
+ #expect(!payload.isEligibleForIntroOffer(for: group))
+ }
+
+ @Test
+ func groupEligibleWithMixedGroupAndNonGroupPurchases() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", trial: false, intro: false),
+ makePurchase(productIdentifier: "com.example.other", trial: true, intro: true),
+ ])
+ let group: Set = ["com.example.monthly", "com.example.yearly"]
+ #expect(payload.isEligibleForIntroOffer(for: group))
+ }
+}
+
+// MARK: - Helpers
+
+private func makePurchase(
+ productIdentifier: String,
+ trial: Bool,
+ intro: Bool
+) -> InAppPurchase {
+ InAppPurchase(
+ productIdentifier: productIdentifier,
+ productType: .autoRenewableSubscription,
+ transactionIdentifier: UUID().uuidString,
+ originalTransactionIdentifier: UUID().uuidString,
+ purchaseDate: Date(),
+ originalPurchaseDate: Date(),
+ subscriptionExpirationDate: nil,
+ cancellationDate: nil,
+ subscriptionTrialPeriod: trial,
+ subscriptionIntroductoryPricePeriod: intro,
+ webOrderLineItemID: nil,
+ promotionalOfferIdentifier: nil,
+ quantity: 1
+ )
+}
+
+private func makePayload(purchases: [InAppPurchase]) -> InAppReceiptPayload {
+ InAppReceiptPayload(
+ bundleIdentifier: "com.example.app",
+ appVersion: "1.0",
+ originalAppVersion: "1.0",
+ originalPurchaseDate: Date(),
+ purchases: purchases,
+ expirationDate: nil,
+ bundleIdentifierData: Data(),
+ opaqueValue: Data(),
+ receiptHash: Data(),
+ creationDate: Date(),
+ ageRating: nil,
+ environment: .sandbox
+ )
+}
diff --git a/Tests/Extras/PurchaseQueryTests.swift b/Tests/Extras/PurchaseQueryTests.swift
new file mode 100644
index 0000000..bf09b54
--- /dev/null
+++ b/Tests/Extras/PurchaseQueryTests.swift
@@ -0,0 +1,364 @@
+import Foundation
+import Testing
+
+@testable import TPInAppReceipt
+
+@Suite("PurchaseQueries")
+struct PurchaseQueryTests {
+ // MARK: - containsPurchase
+
+ @Test
+ func containsPurchaseWhenProductExists() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly")
+ ])
+ #expect(payload.containsPurchase(ofProductIdentifier: "com.example.monthly"))
+ }
+
+ @Test
+ func doesNotContainPurchaseWhenProductAbsent() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly")
+ ])
+ #expect(!payload.containsPurchase(ofProductIdentifier: "com.example.yearly"))
+ }
+
+ @Test
+ func doesNotContainPurchaseWhenEmpty() {
+ let payload = makePayload(purchases: [])
+ #expect(!payload.containsPurchase(ofProductIdentifier: "com.example.monthly"))
+ }
+
+ // MARK: - purchases(ofProductIdentifier:)
+
+ @Test
+ func filtersToMatchingProduct() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly"),
+ makePurchase(productIdentifier: "com.example.yearly"),
+ makePurchase(productIdentifier: "com.example.monthly"),
+ ])
+ let result = payload.purchases(ofProductIdentifier: "com.example.monthly")
+ #expect(result.count == 2)
+ #expect(result.allSatisfy { $0.productIdentifier == "com.example.monthly" })
+ }
+
+ @Test
+ func defaultSortIsDateDescending() {
+ let now = Date()
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", purchaseDate: now.addingTimeInterval(-7200)),
+ makePurchase(productIdentifier: "com.example.monthly", purchaseDate: now),
+ makePurchase(productIdentifier: "com.example.monthly", purchaseDate: now.addingTimeInterval(-3600)),
+ ])
+ let result = payload.purchases(ofProductIdentifier: "com.example.monthly")
+ #expect(result.count == 3)
+ #expect(result[0].purchaseDate == now)
+ #expect(result[2].purchaseDate == now.addingTimeInterval(-7200))
+ }
+
+ @Test
+ func customSortAscending() {
+ let now = Date()
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", purchaseDate: now),
+ makePurchase(productIdentifier: "com.example.monthly", purchaseDate: now.addingTimeInterval(-3600)),
+ ])
+ let result = payload.purchases(ofProductIdentifier: "com.example.monthly") {
+ $0.purchaseDate < $1.purchaseDate
+ }
+ #expect(result[0].purchaseDate < result[1].purchaseDate)
+ }
+
+ @Test
+ func returnsEmptyWhenNoMatch() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.yearly")
+ ])
+ #expect(payload.purchases(ofProductIdentifier: "com.example.monthly").isEmpty)
+ }
+
+ // MARK: - originalTransactionIdentifier
+
+ @Test
+ func returnsOriginalTransactionId() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly", originalTxId: "orig-001")
+ ])
+ #expect(payload.originalTransactionIdentifier(ofProductIdentifier: "com.example.monthly") == "orig-001")
+ }
+
+ @Test
+ func returnsNilForMissingProduct() {
+ let payload = makePayload(purchases: [])
+ #expect(payload.originalTransactionIdentifier(ofProductIdentifier: "com.example.monthly") == nil)
+ }
+
+ // MARK: - activeAutoRenewableSubscriptionPurchases
+
+ @Test
+ func findsActiveSubscription() {
+ let now = Date()
+ let payload = makePayload(purchases: [
+ makeSubscription(
+ productIdentifier: "com.example.monthly",
+ purchaseDate: now.addingTimeInterval(-3600),
+ expirationDate: now.addingTimeInterval(3600)
+ )
+ ])
+ let active = payload.activeAutoRenewableSubscriptionPurchases(
+ ofProductIdentifier: "com.example.monthly",
+ forDate: now
+ )
+ #expect(active != nil)
+ #expect(active?.productIdentifier == "com.example.monthly")
+ }
+
+ @Test
+ func returnsNilWhenSubscriptionExpired() {
+ let now = Date()
+ let payload = makePayload(purchases: [
+ makeSubscription(
+ productIdentifier: "com.example.monthly",
+ purchaseDate: now.addingTimeInterval(-7200),
+ expirationDate: now.addingTimeInterval(-3600)
+ )
+ ])
+ let active = payload.activeAutoRenewableSubscriptionPurchases(
+ ofProductIdentifier: "com.example.monthly",
+ forDate: now
+ )
+ #expect(active == nil)
+ }
+
+ // MARK: - lastAutoRenewableSubscriptionPurchase
+
+ @Test
+ func returnsSubscriptionWithLatestExpiration() {
+ let now = Date()
+ let payload = makePayload(purchases: [
+ makeSubscription(
+ productIdentifier: "com.example.monthly",
+ purchaseDate: now.addingTimeInterval(-7200),
+ expirationDate: now.addingTimeInterval(-3600)
+ ),
+ makeSubscription(
+ productIdentifier: "com.example.monthly",
+ purchaseDate: now.addingTimeInterval(-3600),
+ expirationDate: now.addingTimeInterval(3600)
+ ),
+ ])
+ let last = payload.lastAutoRenewableSubscriptionPurchase(
+ ofProductIdentifier: "com.example.monthly"
+ )
+ #expect(last?.subscriptionExpirationDate == now.addingTimeInterval(3600))
+ }
+
+ @Test
+ func returnsNilWhenNoSubscriptions() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.consumable")
+ ])
+ #expect(
+ payload.lastAutoRenewableSubscriptionPurchase(
+ ofProductIdentifier: "com.example.consumable"
+ ) == nil
+ )
+ }
+
+ // MARK: - hasActiveAutoRenewableSubscription
+
+ @Test
+ func hasActiveSubscriptionWhenActive() {
+ let now = Date()
+ let payload = makePayload(purchases: [
+ makeSubscription(
+ productIdentifier: "com.example.monthly",
+ purchaseDate: now.addingTimeInterval(-3600),
+ expirationDate: now.addingTimeInterval(3600)
+ )
+ ])
+ #expect(
+ payload.hasActiveAutoRenewableSubscription(
+ ofProductIdentifier: "com.example.monthly",
+ forDate: now
+ )
+ )
+ }
+
+ @Test
+ func noActiveSubscriptionWhenAllExpired() {
+ let now = Date()
+ let payload = makePayload(purchases: [
+ makeSubscription(
+ productIdentifier: "com.example.monthly",
+ purchaseDate: now.addingTimeInterval(-7200),
+ expirationDate: now.addingTimeInterval(-3600)
+ )
+ ])
+ #expect(
+ !payload.hasActiveAutoRenewableSubscription(
+ ofProductIdentifier: "com.example.monthly",
+ forDate: now
+ )
+ )
+ }
+
+ // MARK: - hasPurchases / autoRenewablePurchases
+
+ @Test
+ func hasPurchasesWhenNonEmpty() {
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.monthly")
+ ])
+ #expect(payload.hasPurchases)
+ }
+
+ @Test
+ func hasPurchasesIsFalseWhenEmpty() {
+ let payload = makePayload(purchases: [])
+ #expect(!payload.hasPurchases)
+ }
+
+ @Test
+ func autoRenewablePurchasesFiltersCorrectly() {
+ let now = Date()
+ let payload = makePayload(purchases: [
+ makePurchase(productIdentifier: "com.example.consumable"),
+ makeSubscription(
+ productIdentifier: "com.example.monthly",
+ purchaseDate: now.addingTimeInterval(-3600),
+ expirationDate: now.addingTimeInterval(3600)
+ ),
+ ])
+ let autoRenewable = payload.autoRenewablePurchases
+ #expect(autoRenewable.count == 1)
+ #expect(autoRenewable[0].productIdentifier == "com.example.monthly")
+ }
+}
+
+// MARK: - Helpers
+
+/// Wraps `InAppReceiptPayload` to expose AppReceipt-like query methods for testing
+/// without needing a real PKCS#7 structure.
+private struct PayloadQueryWrapper {
+ let payload: InAppReceiptPayload
+
+ var purchases: [InAppPurchase] { payload.purchases }
+ var hasPurchases: Bool { !purchases.isEmpty }
+
+ var autoRenewablePurchases: [InAppPurchase] {
+ purchases.filter { $0.isRenewableSubscription }
+ }
+
+ func containsPurchase(ofProductIdentifier productIdentifier: String) -> Bool {
+ purchases.contains { $0.productIdentifier == productIdentifier }
+ }
+
+ func purchases(
+ ofProductIdentifier productIdentifier: String,
+ sortedBy sort: ((InAppPurchase, InAppPurchase) -> Bool)? = nil
+ ) -> [InAppPurchase] {
+ let filtered = purchases.filter { $0.productIdentifier == productIdentifier }
+ guard let sort else {
+ return filtered.sorted { $0.purchaseDate > $1.purchaseDate }
+ }
+ return filtered.sorted { sort($0, $1) }
+ }
+
+ func originalTransactionIdentifier(ofProductIdentifier productIdentifier: String) -> String? {
+ purchases(ofProductIdentifier: productIdentifier)
+ .first?
+ .originalTransactionIdentifier
+ }
+
+ func activeAutoRenewableSubscriptionPurchases(
+ ofProductIdentifier productIdentifier: String,
+ forDate date: Date
+ ) -> InAppPurchase? {
+ purchases(ofProductIdentifier: productIdentifier)
+ .first { $0.isActiveAutoRenewableSubscription(forDate: date) }
+ }
+
+ func lastAutoRenewableSubscriptionPurchase(
+ ofProductIdentifier productIdentifier: String
+ ) -> InAppPurchase? {
+ purchases(ofProductIdentifier: productIdentifier)
+ .filter { $0.subscriptionExpirationDate != nil }
+ .max { ($0.subscriptionExpirationDate ?? .distantPast) < ($1.subscriptionExpirationDate ?? .distantPast) }
+ }
+
+ func hasActiveAutoRenewableSubscription(
+ ofProductIdentifier productIdentifier: String,
+ forDate date: Date
+ ) -> Bool {
+ activeAutoRenewableSubscriptionPurchases(
+ ofProductIdentifier: productIdentifier,
+ forDate: date
+ ) != nil
+ }
+}
+
+private func makePayload(purchases: [InAppPurchase]) -> PayloadQueryWrapper {
+ PayloadQueryWrapper(
+ payload: InAppReceiptPayload(
+ bundleIdentifier: "com.example.app",
+ appVersion: "1.0",
+ originalAppVersion: "1.0",
+ originalPurchaseDate: Date(),
+ purchases: purchases,
+ expirationDate: nil,
+ bundleIdentifierData: Data(),
+ opaqueValue: Data(),
+ receiptHash: Data(),
+ creationDate: Date(),
+ ageRating: nil,
+ environment: .sandbox
+ )
+ )
+}
+
+private func makePurchase(
+ productIdentifier: String,
+ purchaseDate: Date = Date(),
+ originalTxId: String = UUID().uuidString
+) -> InAppPurchase {
+ InAppPurchase(
+ productIdentifier: productIdentifier,
+ productType: .nonConsumable,
+ transactionIdentifier: UUID().uuidString,
+ originalTransactionIdentifier: originalTxId,
+ purchaseDate: purchaseDate,
+ originalPurchaseDate: purchaseDate,
+ subscriptionExpirationDate: nil,
+ cancellationDate: nil,
+ subscriptionTrialPeriod: false,
+ subscriptionIntroductoryPricePeriod: false,
+ webOrderLineItemID: nil,
+ promotionalOfferIdentifier: nil,
+ quantity: 1
+ )
+}
+
+private func makeSubscription(
+ productIdentifier: String,
+ purchaseDate: Date,
+ expirationDate: Date,
+ cancellationDate: Date? = nil
+) -> InAppPurchase {
+ InAppPurchase(
+ productIdentifier: productIdentifier,
+ productType: .autoRenewableSubscription,
+ transactionIdentifier: UUID().uuidString,
+ originalTransactionIdentifier: UUID().uuidString,
+ purchaseDate: purchaseDate,
+ originalPurchaseDate: purchaseDate,
+ subscriptionExpirationDate: expirationDate,
+ cancellationDate: cancellationDate,
+ subscriptionTrialPeriod: false,
+ subscriptionIntroductoryPricePeriod: false,
+ webOrderLineItemID: nil,
+ promotionalOfferIdentifier: nil,
+ quantity: 1
+ )
+}
diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift
deleted file mode 100644
index 86a148a..0000000
--- a/Tests/LinuxMain.swift
+++ /dev/null
@@ -1,7 +0,0 @@
-import XCTest
-
-import TPInAppReceiptTests
-
-var tests = [XCTestCaseEntry]()
-tests += TPInAppReceiptTests.allTests()
-XCTMain(tests)
diff --git a/Tests/Mocks.swift b/Tests/Mocks.swift
new file mode 100644
index 0000000..98e02b5
--- /dev/null
+++ b/Tests/Mocks.swift
@@ -0,0 +1,24 @@
+import Foundation
+import Testing
+
+@testable import TPInAppReceipt
+
+// MARK: - Mock Verifiers
+
+struct MockValidVerifier: ReceiptVerifier {
+ func verify(_: ReceiptValidatable) async -> VerificationResult {
+ .valid
+ }
+}
+
+struct MockInvalidVerifier: ReceiptVerifier {
+ let error: Error
+
+ func verify(_: ReceiptValidatable) async -> VerificationResult {
+ .invalid(error)
+ }
+}
+
+struct MockVerificationError: Error, Equatable {
+ let message: String
+}
diff --git a/Tests/TPInAppReceiptTests/Device.swift b/Tests/TPInAppReceiptTests/Device.swift
deleted file mode 100644
index 317e617..0000000
--- a/Tests/TPInAppReceiptTests/Device.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-//
-// Device.swift
-// TPInAppReceiptTests
-//
-// Created by Pavel Tikhonenko on 19.06.2020.
-// Copyright © 2020 Pavel Tikhonenko. All rights reserved.
-//
-
-import Foundation
-
-protocol Device
-{
- var receipt: Data { get }
- var uuid: String { get }
-}
diff --git a/Tests/TPInAppReceiptTests/PerformanceTests.swift b/Tests/TPInAppReceiptTests/PerformanceTests.swift
deleted file mode 100644
index 7ba83a9..0000000
--- a/Tests/TPInAppReceiptTests/PerformanceTests.swift
+++ /dev/null
@@ -1,85 +0,0 @@
-//
-// ASN1Tests.swift
-// TPInAppReceipt
-//
-// Created by Pavel Tikhonenko on 19.06.20.
-// Copyright © 2019-2020 Pavel Tikhonenko. All rights reserved.
-//
-
-import XCTest
-@testable import TPInAppReceipt
-
-class PerformanceTests: XCTestCase
-{
- var receipt: InAppReceipt!
-
- override func setUp()
- {
- receipt = try! InAppReceipt(receiptData: legacyReceipt)
- }
-
- override func tearDown() {
- // Put teardown code here. This method is called after the invocation of each test method in the class.
- }
-
- func testParsingPerformance() { // / 0.004
- // This is an example of a performance test case.
- self.measure {
- do
- {
- let receipt = try InAppReceipt(receiptData: legacyReceipt)
- }catch{
- XCTFail("Unable to parse: \(error)")
- }
- }
- }
-
- func testValidationPerformance() { // / 0.004
- // This is an example of a performance test case.
- self.measure {
- do
- {
- try receipt.validate()
- }catch{
- XCTFail("Unable to verify: \(error)")
- }
- }
- }
-
- func testHashValidationPerformance() { // 0.000022 // 0.000034
- // This is an example of a performance test case.
- self.measure {
- do
- {
- try receipt.verifyHash()
- }catch{
- XCTFail("Unable to verify: \(error)")
- }
- }
- }
-
- func testSignatureValidationPerformance() { // 0.006 // 0.002499
- // This is an example of a performance test case.
- self.measure {
- do
- {
- try receipt.verifySignature()
- }catch{
- XCTFail("Unable to verify: \(error)")
- }
- }
- }
-
- func testBundleValidationPerformance() { // 0.000012 // 0.000007
- // This is an example of a performance test case.
- self.measure {
- do
- {
- try receipt.verifyBundleIdentifierAndVersion()
- }catch{
- XCTFail("Unable to verify: \(error)")
- }
- }
- }
-
-}
diff --git a/Tests/TPInAppReceiptTests/ReceiptData.swift b/Tests/TPInAppReceiptTests/ReceiptData.swift
deleted file mode 100644
index f96bd26..0000000
--- a/Tests/TPInAppReceiptTests/ReceiptData.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// Receipt.swift
-// asn1swiftTests
-//
-// Created by Pavel Tikhonenko on 03.08.2020.
-//
-
-import Foundation
-
-var noOriginalPurchaseDateCrashReceipt: Data = Data(base64Encoded: "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")!
-
-var crashReceipt: Data = Data(base64Encoded: "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")!
-
-var watchReceipt: Data = Data(base64Encoded: "MIIUCQYJKoZIhvcNAQcCoIIT+jCCE/YCAQExCzAJBgUrDgMCGgUAMIIDqgYJKoZIhvcNAQcBoIIDmwSCA5cxggOTMAoCARQCAQEEAgwAMAsCARkCAQEEAwIBAzAMAgEKAgEBBAQWAjQrMAwCAQ4CAQEEBAICAJswDAIBDwIBAQQEAgInDzANAgELAgEBBAUCAwWh8zANAgENAgEBBAUCAwERcDAOAgEBAgEBBAYCBBmI1N8wDgIBCQIBAQQGAgRQMjU1MA4CARACAQEEBgIEMfDs7DAQAgEDAgEBBAgMBjMyLjMuMTARAgETAgEBBAkMBzMxLjEyLjEwFAIBAAIBAQQMDApQcm9kdWN0aW9uMBgCAQQCAQIEEMP1Q9JcaPNiB8NT5OCCsM8wHAIBBQIBAQQULy1o3/snYoeBSX40cOs3Zt6xEUcwHgIBCAIBAQQWFhQyMDIwLTA5LTIxVDA1OjM1OjE5WjAeAgEMAgEBBBYWFDIwMjAtMDktMjFUMDU6MzU6MTlaMB4CARICAQEEFhYUMjAyMC0wOC0wOVQxMzowODozMFowIwIBAgIBAQQbDBluZXQuemFjaGFyaWFkaXMuY3ljbGVtYXBzMDkCAQcCAQEEMY7ohWrWQ6O1l6RMxarQlijJlK93bg2jA4DUB4sXCwiejkkAfoDZ9yR/O4D4rFr5oC8wSwIBBgIBAQRD1UwqJzHMuoDOND199y5H1fu1O+QFZ96S2Te5LztSxHv/Kx4wGujz1y9nfmv/I9k0IjpuDjQX+9PmLITMMKr7UG1FxDCCAX4CARECAQEEggF0MYIBcDALAgIGrQIBAQQCDAAwCwICBrACAQEEAhYAMAsCAgayAgEBBAIMADALAgIGswIBAQQCDAAwCwICBrQCAQEEAgwAMAsCAga1AgEBBAIMADALAgIGtgIBAQQCDAAwDAICBqUCAQEEAwIBATAMAgIGqwIBAQQDAgEDMAwCAgaxAgEBBAMCAQAwDAICBrcCAQEEAwIBADAPAgIGrgIBAQQGAgRV8AbdMBECAgavAgEBBAgCBmQLce2iDTAaAgIGpwIBAQQRDA8xMTAwMDA4MjI0MTY4MzUwGgICBqkCAQEEEQwPMTEwMDAwODIyNDE2ODM1MBwCAgamAgEBBBMMEUNZQ0xFTUFQU19QUkVNSVVNMB8CAgaoAgEBBBYWFDIwMjAtMDktMTBUMTk6NDc6NTBaMB8CAgaqAgEBBBYWFDIwMjAtMDktMTBUMTk6NDc6NTJaMB8CAgasAgEBBBYWFDIwMjEtMDktMTBUMTk6NDc6NTBaoIIOZTCCBXwwggRkoAMCAQICCA7rV4fnngmNMA0GCSqGSIb3DQEBBQUAMIGWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5jLjEsMCoGA1UECwwjQXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMxRDBCBgNVBAMMO0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE1MTExMzAyMTUwOVoXDTIzMDIwNzIxNDg0N1owgYkxNzA1BgNVBAMMLk1hYyBBcHAgU3RvcmUgYW5kIGlUdW5lcyBTdG9yZSBSZWNlaXB0IFNpZ25pbmcxLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zMRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKXPgf0looFb1oftI9ozHI7iI8ClxCbLPcaf7EoNVYb/pALXl8o5VG19f7JUGJ3ELFJxjmR7gs6JuknWCOW0iHHPP1tGLsbEHbgDqViiBD4heNXbt9COEo2DTFsqaDeTwvK9HsTSoQxKWFKrEuPt3R+YFZA1LcLMEsqNSIH3WHhUa+iMMTYfSgYMR1TzN5C4spKJfV+khUrhwJzguqS7gpdj9CuTwf0+b8rB9Typj1IawCUKdg7e/pn+/8Jr9VterHNRSQhWicxDkMyOgQLQoJe2XLGhaWmHkBBoJiY5uB0Qc7AKXcVz0N92O9gt2Yge4+wHz+KO0NP6JlWB7+IDSSMCAwEAAaOCAdcwggHTMD8GCCsGAQUFBwEBBDMwMTAvBggrBgEFBQcwAYYjaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwMy13d2RyMDQwHQYDVR0OBBYEFJGknPzEdrefoIr0TfWPNl3tKwSFMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUiCcXCam2GGCL7Ou69kdZxVJUo7cwggEeBgNVHSAEggEVMIIBETCCAQ0GCiqGSIb3Y2QFBgEwgf4wgcMGCCsGAQUFBwICMIG2DIGzUmVsaWFuY2Ugb24gdGhpcyBjZXJ0aWZpY2F0ZSBieSBhbnkgcGFydHkgYXNzdW1lcyBhY2NlcHRhbmNlIG9mIHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5kIGNvbmRpdGlvbnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kgYW5kIGNlcnRpZmljYXRpb24gcHJhY3RpY2Ugc3RhdGVtZW50cy4wNgYIKwYBBQUHAgEWKmh0dHA6Ly93d3cuYXBwbGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5LzAOBgNVHQ8BAf8EBAMCB4AwEAYKKoZIhvdjZAYLAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAA2mG9MuPeNbKwduQpZs0+iMQzCCX+Bc0Y2+vQ+9GvwlktuMhcOAWd/j4tcuBRSsDdu2uP78NS58y60Xa45/H+R3ubFnlbQTXqYZhnb4WiCV52OMD3P86O3GH66Z+GVIXKDgKDrAEDctuaAEOR9zucgF/fLefxoqKm4rAfygIFzZ630npjP49ZjgvkTbsUxn/G4KT8niBqjSl/OnjmtRolqEdWXRFgRi48Ff9Qipz2jZkgDJwYyz+I0AZLpYYMB8r491ymm5WyrWHWhumEL1TKc3GZvMOxx6GUPzo22/SGAGDDaSK+zeGLUR2i0j0I78oGmcFxuegHs5R0UwYS/HE6gwggQiMIIDCqADAgECAggB3rzEOW2gEDANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMTMwMjA3MjE0ODQ3WhcNMjMwMjA3MjE0ODQ3WjCBljELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zMUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMo4VKbLVqrIJDlI6Yzu7F+4fyaRvDRTes58Y4Bhd2RepQcjtjn+UC0VVlhwLX7EbsFKhT4v8N6EGqFXya97GP9q+hUSSRUIGayq2yoy7ZZjaFIVPYyK7L9rGJXgA6wBfZcFZ84OhZU3au0Jtq5nzVFkn8Zc0bxXbmc1gHY2pIeBbjiP2CsVTnsl2Fq/ToPBjdKT1RpxtWCcnTNOVfkSWAyGuBYNweV3RY1QSLorLeSUheHoxJ3GaKWwo/xnfnC6AllLd0KRObn1zeFM78A7SIym5SFd/Wpqu6cWNWDS5q3zRinJ6MOL6XnAamFnFbLw/eVovGJfbs+Z3e8bY/6SZasCAwEAAaOBpjCBozAdBgNVHQ4EFgQUiCcXCam2GGCL7Ou69kdZxVJUo7cwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQr0GlHlHYJ/vRrjS5ApvdHTX8IXjAuBgNVHR8EJzAlMCOgIaAfhh1odHRwOi8vY3JsLmFwcGxlLmNvbS9yb290LmNybDAOBgNVHQ8BAf8EBAMCAYYwEAYKKoZIhvdjZAYCAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAE/P71m+LPWybC+P7hOHMugFNahui33JaQy52Re8dyzUZ+L9mm06WVzfgwG9sq4qYXKxr83DRTCPo4MNzh1HtPGTiqN0m6TDmHKHOz6vRQuSVLkyu5AYU2sKThC22R1QbCGAColOV4xrWzw9pv3e9w0jHQtKJoc/upGSTKQZEhltV/V6WId7aIrkhoxK6+JJFKql3VUAqa67SzCu4aCxvCmA5gl35b40ogHKf9ziCuY7uLvsumKV8wVjQYLNDzsdTJWk26v5yZXpT+RN5yaZgem8+bQp0gF6ZuEujPYhisX4eOGBrr/TkJ2prfOv/TgalmcwHFGlXOxxioK0bA8MFR8wggS7MIIDo6ADAgECAgECMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMSYwJAYDVQQLEx1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEWMBQGA1UEAxMNQXBwbGUgUm9vdCBDQTAeFw0wNjA0MjUyMTQwMzZaFw0zNTAyMDkyMTQwMzZaMGIxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMSYwJAYDVQQLEx1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEWMBQGA1UEAxMNQXBwbGUgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOSRqQkfkdseR1DrBe1eeYQt6zaiV0xV7IsZid75S2z1B6siMALoGD74UAnTf0GomPnRymacJGsR0KO75Bsqwx+VnnoMpEeLW9QWNzPLxA9NzhRp0ckZcvVdDtV/X5vyJQO6VY9NXQ3xZDUjFUsVWR2zlPf2nJ7PULrBWFBnjwi0IPfLrCwgb3C2PwEwjLdDzw+dPfMrSSgayP7OtbkO2V4c1ss9tTqt9A8OAJILsSEWLnTVPA3bYharo3GSR1NVwa8vQbP4++NwzeajTEV+H0xrUJZBicR0YgsQg0GHM4qBsTBY7FoEMoxos48d3mVz/2deZbxJ2HafMxRloXeUyS0CAwEAAaOCAXowggF2MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQr0GlHlHYJ/vRrjS5ApvdHTX8IXjAfBgNVHSMEGDAWgBQr0GlHlHYJ/vRrjS5ApvdHTX8IXjCCAREGA1UdIASCAQgwggEEMIIBAAYJKoZIhvdjZAUBMIHyMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5hcHBsZS5jb20vYXBwbGVjYS8wgcMGCCsGAQUFBwICMIG2GoGzUmVsaWFuY2Ugb24gdGhpcyBjZXJ0aWZpY2F0ZSBieSBhbnkgcGFydHkgYXNzdW1lcyBhY2NlcHRhbmNlIG9mIHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5kIGNvbmRpdGlvbnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kgYW5kIGNlcnRpZmljYXRpb24gcHJhY3RpY2Ugc3RhdGVtZW50cy4wDQYJKoZIhvcNAQEFBQADggEBAFw2mUwteLftjJvc83eb8nbSdzBPwR+Fg4UbmT1HN/Kpm0COLNSxkBLYvvRzm+7SZA/LeU802KI++Xj/a8gH7H05g4tTINM4xLG/mk8Ka/8r/FmnBQl8F0BWER5007eLIztHo9VvJOLr0bdw3w9F4SfK8W147ee1Fxeo3H4iNcol1dkP1mvUoiQjEfehrI9zgWDGG1sJL5Ky+ERI8GA4nhX1PSZnIIozavcNgs/e66Mv+VNqW2TAYzN39zoHLFbr2g8hDtq6cxlPtdk2f8GHVdmnmbkyQvvY1XGefqFStxu9k0IkEirHDx22TZxeY8hLgBdQqorV2uT80AkHN7B1dSExggHLMIIBxwIBATCBozCBljELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zMUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQIIDutXh+eeCY0wCQYFKw4DAhoFADANBgkqhkiG9w0BAQEFAASCAQBs3P4UZu6rBmoV0gjUyAKa/ko10JKWmjKCOMKnWr89OdxvSdXzqu6wbFIALC5DHSsVxIkFh+Vk5jZVwWYPWq77IMGm2U6g8c+2dXBOyH90L+6rxrp5KxoFjarhSGDae556sWVj3xSF+6WoEUYqUTVRpOFlNNAISakEmR9nDIMH02F9weLKvCmn+3D5oXA69MzBajpfKEAGgJaL9aCpTti22aTulDBSLETqRITbklBd4kuzmg+QtEmcz8uHRAuHfiIA9Xjyrf60hZgdOwkD5MRTtSTe+jtO8zfgLwMjN/zYUoD2JGAWryG3QotBxtwTB+uBVoiAvN4MOl48kOnStkMH")!
-
-var newReceipt: Data = Data(base64Encoded: "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")!
-
-var legacyReceipt: Data = Data(base64Encoded: "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")!
diff --git a/Tests/TPInAppReceiptTests/TPInAppReceiptTests.swift b/Tests/TPInAppReceiptTests/TPInAppReceiptTests.swift
deleted file mode 100644
index 884a22c..0000000
--- a/Tests/TPInAppReceiptTests/TPInAppReceiptTests.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-import XCTest
-@testable import TPInAppReceipt
-
-final class TPInAppReceiptTests: XCTestCase {
- func testCrashReceipts() {
- var r = try? InAppReceipt(receiptData: noOriginalPurchaseDateCrashReceipt)
- }
-
- func testNewReceipt() {
- self.measure {
- let r = try! InAppReceipt(receiptData: newReceipt)
- print(r.creationDate)
- }
-
- }
-
- func testLegacyReceipt() {
- self.measure {
- let r = try! InAppReceipt(receiptData: legacyReceipt)
- }
-
- }
-
- static var allTests = [
- ("testNewReceipt", testNewReceipt)
- ]
-}
diff --git a/Tests/TPInAppReceiptTests/XCTestManifests.swift b/Tests/TPInAppReceiptTests/XCTestManifests.swift
deleted file mode 100644
index 53e1f12..0000000
--- a/Tests/TPInAppReceiptTests/XCTestManifests.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-import XCTest
-
-#if !canImport(ObjectiveC)
-public func allTests() -> [XCTestCaseEntry] {
- return [
- testCase(TPInAppReceiptTests.allTests),
- ]
-}
-#endif
diff --git a/Tests/TestingUtility.swift b/Tests/TestingUtility.swift
new file mode 100644
index 0000000..32f2d45
--- /dev/null
+++ b/Tests/TestingUtility.swift
@@ -0,0 +1,48 @@
+import Foundation
+import SwiftASN1
+import X509
+
+@testable import TPInAppReceipt
+
+public enum TestingUtility {
+ public static func readFile(_ path: String) -> String {
+ let absolutePath = Bundle.module.url(forResource: path, withExtension: "")!
+ return try! String(contentsOf: absolutePath, encoding: .utf8)
+ }
+
+ public static func readBytes(_ path: String) -> Data {
+ let absolutePath = Bundle.module.url(forResource: path, withExtension: "")!
+ return try! Data(contentsOf: absolutePath)
+ }
+
+ public static func readReceipt(_ path: String) -> Data {
+ let file = readFile(path)
+ return Data(base64Encoded: file)!
+ }
+
+ public static func parseReceipt(_ path: String) throws -> AppReceipt {
+ let receiptData = TestingUtility.readReceipt(path)
+ let result = try BER.parse(Array(receiptData))
+ return try AppReceipt(berEncoded: result)
+ }
+
+ public static func certificateToData(_ certificate: Certificate) throws -> Data {
+ var serializer = DER.Serializer()
+ try serializer.serialize(certificate)
+ return Data(serializer.serializedBytes)
+ }
+
+ public static func loadRootCertificate() -> Data {
+ TestingUtility.readBytes("Assets/AppleIncRootCertificate.cer")
+ }
+
+ public static func loadXcodeRootCertificate() -> Data {
+ TestingUtility.readBytes("Assets/StoreKitTestCertificate.cer")
+ }
+}
+
+extension Data {
+ var bytes: [UInt8] {
+ Array(self)
+ }
+}
diff --git a/Tests/Validation/ReceiptValidatorTests.swift b/Tests/Validation/ReceiptValidatorTests.swift
new file mode 100644
index 0000000..4887e34
--- /dev/null
+++ b/Tests/Validation/ReceiptValidatorTests.swift
@@ -0,0 +1,44 @@
+import Foundation
+import Testing
+
+@testable import TPInAppReceipt
+
+@Suite("ReceiptValidator")
+struct ReceiptValidatorTests {
+ @Test
+ func validationSucceedsWhenAllVerifiersPass() async throws {
+ let validator = ReceiptValidator {
+ MockValidVerifier()
+ MockValidVerifier()
+ MockValidVerifier()
+ }
+
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let result = await validator.validate(receipt)
+
+ #expect(result.isValid)
+ }
+
+ @Test
+ func validationFailsWhenAnyVerifierFails() async throws {
+ let expectedError = MockVerificationError(message: "Verification failed")
+
+ let validator = ReceiptValidator {
+ MockValidVerifier()
+ MockInvalidVerifier(error: expectedError)
+ MockValidVerifier()
+ }
+
+ let receipt = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let result = await validator.validate(receipt)
+
+ #expect(result.isInvalid)
+
+ if case let .invalid(error) = result {
+ #expect(error is MockVerificationError)
+ if let mockError = error as? MockVerificationError {
+ #expect(mockError == expectedError)
+ }
+ }
+ }
+}
diff --git a/Tests/Validation/VerifierBuilderTests.swift b/Tests/Validation/VerifierBuilderTests.swift
new file mode 100644
index 0000000..d3629f6
--- /dev/null
+++ b/Tests/Validation/VerifierBuilderTests.swift
@@ -0,0 +1,54 @@
+import Foundation
+import Testing
+
+@testable import TPInAppReceipt
+
+@Suite("VerifierBuilder")
+struct VerifierBuilderTests {
+ @Test
+ func emptyBuilderProducesNoVerifiers() {
+ let validator = ReceiptValidator {}
+
+ #expect(validator.verifiers.count == 0)
+ }
+
+ @Test
+ func builderPreservesOrder() {
+ let error1 = MockVerificationError(message: "First")
+ let error2 = MockVerificationError(message: "Second")
+ let error3 = MockVerificationError(message: "Third")
+
+ let validator = ReceiptValidator {
+ MockInvalidVerifier(error: error1)
+ MockInvalidVerifier(error: error2)
+ MockInvalidVerifier(error: error3)
+ }
+
+ #expect(validator.verifiers.count == 3)
+ let verifier1 = validator.verifiers[0] as! MockInvalidVerifier
+ let verifier2 = validator.verifiers[1] as! MockInvalidVerifier
+ let verifier3 = validator.verifiers[2] as! MockInvalidVerifier
+
+ #expect(verifier1.error as? MockVerificationError == error1)
+ #expect(verifier2.error as? MockVerificationError == error2)
+ #expect(verifier3.error as? MockVerificationError == error3)
+ }
+
+ @Test
+ func builderComposesConditionalAndLoopVerifiers() {
+ let includeOptional = true
+ let extras = [MockValidVerifier(), MockValidVerifier()]
+
+ let validator = ReceiptValidator {
+ MockValidVerifier()
+ if includeOptional {
+ MockValidVerifier()
+ }
+ for verifier in extras {
+ verifier
+ }
+ }
+
+ #expect(validator.verifiers.count == 4)
+ }
+}
diff --git a/Tests/Validation/Verifiers/ChainVerifierTests.swift b/Tests/Validation/Verifiers/ChainVerifierTests.swift
new file mode 100644
index 0000000..ad63f00
--- /dev/null
+++ b/Tests/Validation/Verifiers/ChainVerifierTests.swift
@@ -0,0 +1,209 @@
+import Foundation
+import SwiftASN1
+import Testing
+import X509
+
+@_spi(AsyncValidation) @testable import TPInAppReceipt
+
+// MARK: - Shared Test Cases
+
+typealias ChainVerifyFn = (
+ _ leaf: Certificate,
+ _ intermediate: [Certificate],
+ _ policy: [ReceiptChainVerifierPolicy]
+) async -> TPInAppReceipt.VerificationResult
+
+enum ChainVerifierTestCases {
+ static func validChain(using verify: ChainVerifyFn) async throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let certificates = pkcs7.content.certificates
+
+ let result = await verify(
+ certificates[0],
+ [certificates[1]],
+ [.appleX509Basic, .appStoreReceipt, .validationTime(pkcs7.creationDate)]
+ )
+
+ #expect(result.isValid)
+ }
+
+ static func wrongRootCertificateFails(using verify: ChainVerifyFn) async throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-watch")
+ let certificates = pkcs7.content.certificates
+
+ guard certificates.count >= 2 else {
+ Issue.record("Receipt should contain at least 2 certificates")
+ return
+ }
+
+ let result = await verify(
+ certificates[0],
+ [certificates[1]],
+ [.appleX509Basic, .appStoreReceipt, .validationTime(pkcs7.creationDate)]
+ )
+
+ #expect(result.isInvalid)
+ }
+
+ static func expiredCertificatesFails(using verify: ChainVerifyFn) async throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-legacy")
+ let certificates = pkcs7.content.certificates
+
+ guard certificates.count >= 2 else {
+ Issue.record("Receipt should contain at least 2 certificates")
+ return
+ }
+
+ let result = await verify(
+ certificates[0],
+ [certificates[1]],
+ [.appleX509Basic, .appStoreReceipt, .validationTime(Date())]
+ )
+
+ #expect(result.isInvalid)
+ }
+
+ static func invalidLeafOIDFails(using verify: ChainVerifyFn) async throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-watch")
+ let certificates = pkcs7.content.certificates
+
+ guard certificates.count >= 2 else {
+ Issue.record("Receipt should contain at least 2 certificates")
+ return
+ }
+
+ // Use intermediate as leaf (wrong OID)
+ let result = await verify(
+ certificates[1],
+ [certificates[1]],
+ [.appleX509Basic, .appStoreReceipt, .validationTime(pkcs7.creationDate)]
+ )
+
+ #expect(result.isInvalid)
+ }
+
+ static func invalidIntermediateOIDFails(using verify: ChainVerifyFn) async throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-watch")
+ let certificates = pkcs7.content.certificates
+
+ guard certificates.count >= 2 else {
+ Issue.record("Receipt should contain at least 2 certificates")
+ return
+ }
+
+ // Use leaf as intermediate — leaf cert lacks CA basic constraints
+ let result = await verify(
+ certificates[0],
+ [certificates[0]],
+ [.appleX509Basic, .validationTime(pkcs7.creationDate)]
+ )
+
+ #expect(result.isInvalid)
+ }
+}
+
+// MARK: - X509ChainVerifier
+
+@Suite("X509ChainVerifier")
+struct X509ChainVerifierTests {
+ @Test
+ func validChain() async throws {
+ let verifier = try X509ChainVerifier(rootCertificates: [TestingUtility.loadRootCertificate()])
+ try await ChainVerifierTestCases.validChain { leaf, intermediate, policy in
+ await verifier.verify(leaf: leaf, intermediate: intermediate, policy: policy)
+ }
+ }
+
+ @Test
+ func wrongRootCertificateFails() async throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-watch")
+ let wrongRoot = pkcs7.content.certificates[1]
+ let verifier = X509ChainVerifier(rootCertificates: [wrongRoot])
+ try await ChainVerifierTestCases.wrongRootCertificateFails { leaf, intermediate, policy in
+ await verifier.verify(leaf: leaf, intermediate: intermediate, policy: policy)
+ }
+ }
+
+ @Test
+ func expiredCertificatesFails() async throws {
+ let verifier = try X509ChainVerifier(rootCertificates: [TestingUtility.loadRootCertificate()])
+ try await ChainVerifierTestCases.expiredCertificatesFails { leaf, intermediate, policy in
+ await verifier.verify(leaf: leaf, intermediate: intermediate, policy: policy)
+ }
+ }
+
+ @Test
+ func invalidLeafOIDFails() async throws {
+ let verifier = try X509ChainVerifier(rootCertificates: [TestingUtility.loadRootCertificate()])
+ try await ChainVerifierTestCases.invalidLeafOIDFails { leaf, intermediate, policy in
+ await verifier.verify(leaf: leaf, intermediate: intermediate, policy: policy)
+ }
+ }
+
+ @Test
+ func invalidIntermediateOIDFails() async throws {
+ let verifier = try X509ChainVerifier(rootCertificates: [TestingUtility.loadRootCertificate()])
+ try await ChainVerifierTestCases.invalidIntermediateOIDFails { leaf, intermediate, policy in
+ await verifier.verify(leaf: leaf, intermediate: intermediate, policy: policy)
+ }
+ }
+
+ @Test
+ func invalidCertificateDataThrows() throws {
+ let invalidData = Data([0x00, 0x01, 0x02, 0x03])
+ #expect(throws: (any Error).self) {
+ _ = try X509ChainVerifier(rootCertificates: [invalidData])
+ }
+ }
+}
+
+// MARK: - SecChainVerifier
+
+#if !os(Linux)
+@Suite("SecChainVerifier")
+struct SecChainVerifierTests {
+ @Test
+ func validChain() async throws {
+ let verifier = SecChainVerifier(rootCertificates: [TestingUtility.loadRootCertificate()])
+ try await ChainVerifierTestCases.validChain { leaf, intermediate, policy in
+ verifier.verify(leaf: leaf, intermediate: intermediate, policy: policy)
+ }
+ }
+
+ @Test
+ func wrongRootCertificateFails() async throws {
+ let verifier = SecChainVerifier(rootCertificates: [])
+ try await ChainVerifierTestCases.wrongRootCertificateFails { leaf, intermediate, policy in
+ verifier.verify(leaf: leaf, intermediate: intermediate, policy: policy)
+ }
+ }
+
+ @Test
+ func expiredCertificatesFails() async throws {
+ let verifier = SecChainVerifier(rootCertificates: [TestingUtility.loadRootCertificate()])
+ try await ChainVerifierTestCases.expiredCertificatesFails { leaf, intermediate, policy in
+ verifier.verify(leaf: leaf, intermediate: intermediate, policy: policy)
+ }
+ }
+
+ @Test
+ func invalidLeafOIDFails() async throws {
+ let verifier = SecChainVerifier(rootCertificates: [TestingUtility.loadRootCertificate()])
+ try await ChainVerifierTestCases.invalidLeafOIDFails { leaf, intermediate, policy in
+ verifier.verify(leaf: leaf, intermediate: intermediate, policy: policy)
+ }
+ }
+
+ @Test
+ func invalidIntermediateOIDFails() async throws {
+ #if os(macOS) || targetEnvironment(macCatalyst)
+ // SecTrust on macOS does not enforce CA basic constraints on intermediates
+ #else
+ let verifier = SecChainVerifier(rootCertificates: [TestingUtility.loadRootCertificate()])
+ try await ChainVerifierTestCases.invalidIntermediateOIDFails { leaf, intermediate, policy in
+ verifier.verify(leaf: leaf, intermediate: intermediate, policy: policy)
+ }
+ #endif
+ }
+}
+#endif
diff --git a/Tests/Validation/Verifiers/ErrorPathTests.swift b/Tests/Validation/Verifiers/ErrorPathTests.swift
new file mode 100644
index 0000000..df26f25
--- /dev/null
+++ b/Tests/Validation/Verifiers/ErrorPathTests.swift
@@ -0,0 +1,170 @@
+import Foundation
+import SwiftASN1
+import Testing
+import X509
+
+@testable import TPInAppReceipt
+
+@Suite("Verifier Error Paths")
+struct ErrorPathTests {
+ // MARK: - MetaVerifier
+
+ @Test
+ func metaVerifierPropagatesProviderError() {
+ let verifier = MetaVerifier(
+ appVersionProvider: { throw MetaVerificationError.bundleInfoUnavailable },
+ bundleIdentifierProvider: { "com.example.app" }
+ )
+
+ let result = verifier.verify(
+ bundleIdentifier: "com.example.app",
+ versionIdentifier: "1.0"
+ )
+
+ #expect(result.isInvalid)
+ expectError(result, equals: MetaVerificationError.bundleInfoUnavailable)
+ }
+
+ @Test
+ func metaVerifierPropagatesBundleIdProviderError() {
+ let verifier = MetaVerifier(
+ appVersionProvider: { "1.0" },
+ bundleIdentifierProvider: { throw MetaVerificationError.bundleInfoUnavailable }
+ )
+
+ let result = verifier.verify(
+ bundleIdentifier: "com.example.app",
+ versionIdentifier: "1.0"
+ )
+
+ #expect(result.isInvalid)
+ expectError(result, equals: MetaVerificationError.bundleInfoUnavailable)
+ }
+
+ // MARK: - HashVerifier
+
+ @Test
+ func hashMismatchReturnsSpecificError() {
+ let verifier = HashVerifier(deviceIdentifier: Data(repeating: 0x00, count: 16))
+
+ let result = verifier.verify(
+ expectedHash: Data(repeating: 0xFF, count: 20),
+ opaqueValue: Data(),
+ bundleIdentifier: Data()
+ )
+
+ #expect(result.isInvalid)
+ expectError(result, equals: HashVerificationError.hashMismatch)
+ }
+
+ // MARK: - SignatureVerifier (via ReceiptVerifier)
+
+ @Test
+ func signatureVerifierReturnsInvalidKeyWhenNoCertificates() {
+ let verifier = SignatureVerifier()
+ let receipt = MockReceipt.withNoCertificates()
+
+ let result = verifier.verify(receipt)
+
+ #expect(result.isInvalid)
+ expectError(result, equals: SignatureVerificationError.invalidKey)
+ }
+
+ // MARK: - X509ChainVerifier (via ReceiptVerifier)
+
+ @Test
+ func chainVerifierReturnsInvalidCertificateDataWhenNoCertificates() async throws {
+ let verifier = try X509ChainVerifier(rootCertificates: [TestingUtility.loadRootCertificate()])
+ let receipt = MockReceipt.withNoCertificates()
+
+ let result = await verifier.verify(receipt)
+
+ #expect(result.isInvalid)
+ expectError(result, equals: ChainVerificationError.invalidCertificateData)
+ }
+
+ @Test
+ func chainVerifierReturnsInvalidCertificateDataWhenSingleCertificate() async throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let leaf = pkcs7.content.certificates[0]
+ let verifier = try X509ChainVerifier(rootCertificates: [TestingUtility.loadRootCertificate()])
+ let receipt = MockReceipt.withCertificates([leaf])
+
+ let result = await verifier.verify(receipt)
+
+ #expect(result.isInvalid)
+ expectError(result, equals: ChainVerificationError.invalidCertificateData)
+ }
+}
+
+// MARK: - Assertion Helpers
+
+private func expectError(
+ _ result: TPInAppReceipt.VerificationResult,
+ equals expected: E,
+ sourceLocation: SourceLocation = #_sourceLocation
+) {
+ guard case let .invalid(error) = result else {
+ Issue.record("Expected .invalid but got .valid", sourceLocation: sourceLocation)
+ return
+ }
+ guard let typed = error as? E else {
+ Issue.record(
+ "Expected \(E.self) but got \(type(of: error)): \(error)",
+ sourceLocation: sourceLocation
+ )
+ return
+ }
+ #expect(typed == expected, sourceLocation: sourceLocation)
+}
+
+// MARK: - MockReceipt
+
+private struct MockReceipt: ReceiptValidatable {
+ var environment: InAppReceiptPayload.Environment
+ var versionIdentifier: String
+ var bundleIdentifier: String
+ var bundleIdentifierData: Data
+ var certificates: [Certificate]
+ var certificatesRaw: [Data]
+ var receiptHash: Data
+ var opaqueValue: Data
+ var digestData: Data
+ var digestAlgorithm: DigestAlgorithm
+ var signature: Data
+ var validationTime: Date
+
+ static func withNoCertificates() -> MockReceipt {
+ MockReceipt(
+ environment: .sandbox,
+ versionIdentifier: "1.0",
+ bundleIdentifier: "com.example.app",
+ bundleIdentifierData: Data(),
+ certificates: [],
+ certificatesRaw: [],
+ receiptHash: Data(),
+ opaqueValue: Data(),
+ digestData: Data(),
+ digestAlgorithm: .sha1,
+ signature: Data(),
+ validationTime: Date()
+ )
+ }
+
+ static func withCertificates(_ certs: [Certificate]) -> MockReceipt {
+ MockReceipt(
+ environment: .sandbox,
+ versionIdentifier: "1.0",
+ bundleIdentifier: "com.example.app",
+ bundleIdentifierData: Data(),
+ certificates: certs,
+ certificatesRaw: [],
+ receiptHash: Data(),
+ opaqueValue: Data(),
+ digestData: Data(),
+ digestAlgorithm: .sha1,
+ signature: Data(),
+ validationTime: Date()
+ )
+ }
+}
diff --git a/Tests/Validation/Verifiers/HashVerifierTests.swift b/Tests/Validation/Verifiers/HashVerifierTests.swift
new file mode 100644
index 0000000..4298ba4
--- /dev/null
+++ b/Tests/Validation/Verifiers/HashVerifierTests.swift
@@ -0,0 +1,41 @@
+import Foundation
+import SwiftASN1
+import Testing
+
+@testable import TPInAppReceipt
+
+@Suite("HashVerifier")
+struct HashVerifierTests {
+ let knownDeviceUUID = UUID(uuidString: "956328D9-CC6A-47F4-BE40-6953FB0AB6C7")!
+
+ @Test
+ func verifyHashWithKnownDeviceReceipt() async throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let payload = pkcs7.content.encapContentInfo.eContent!
+
+ let verifier = HashVerifier(deviceIdentifier: knownDeviceUUID.data)
+ let result = verifier.verify(
+ expectedHash: payload.receiptHash,
+ opaqueValue: payload.opaqueValue,
+ bundleIdentifier: payload.bundleIdentifierData
+ )
+
+ #expect(result.isValid)
+ }
+
+ @Test
+ func verificationFailsWithWrongDeviceIdentifier() async throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let payload = pkcs7.content.encapContentInfo.eContent!
+
+ let wrongUUID = UUID(uuidString: "00000000-0000-0000-0000-000000000000")!
+ let verifier = HashVerifier(deviceIdentifier: wrongUUID.data)
+ let result = verifier.verify(
+ expectedHash: payload.receiptHash,
+ opaqueValue: payload.opaqueValue,
+ bundleIdentifier: payload.bundleIdentifierData
+ )
+
+ #expect(result.isInvalid)
+ }
+}
diff --git a/Tests/Validation/Verifiers/MetaVerifierTests.swift b/Tests/Validation/Verifiers/MetaVerifierTests.swift
new file mode 100644
index 0000000..d981994
--- /dev/null
+++ b/Tests/Validation/Verifiers/MetaVerifierTests.swift
@@ -0,0 +1,112 @@
+import Foundation
+import Testing
+
+@_spi(Blocking) @testable import TPInAppReceipt
+
+@Suite("MetaVerifier")
+struct MetaVerifierTests {
+ // MARK: - Valid Verification
+
+ @Test
+ func validVerificationWithMatchingValues() {
+ let verifier = MetaVerifier(
+ expectedBundleIdentifier: "com.example.app",
+ expectedAppVersion: "1.0.0"
+ )
+
+ let result = verifier.verify(
+ bundleIdentifier: "com.example.app",
+ versionIdentifier: "1.0.0"
+ )
+
+ #expect(result.isValid)
+ }
+
+ // MARK: - Invalid Verification
+
+ @Test
+ func mismatchedBundleIdentifierFails() {
+ let verifier = MetaVerifier(
+ expectedBundleIdentifier: "com.example.app",
+ expectedAppVersion: "1.0.0"
+ )
+
+ let result = verifier.verify(
+ bundleIdentifier: "com.different.app",
+ versionIdentifier: "1.0.0"
+ )
+
+ #expect(result.isInvalid)
+ if case let .invalid(error) = result {
+ #expect(error is MetaVerificationError)
+ if let metaError = error as? MetaVerificationError {
+ #expect(metaError == .bundleIdentifierMismatch)
+ }
+ }
+ }
+
+ @Test
+ func mismatchedVersionFails() {
+ let verifier = MetaVerifier(
+ expectedBundleIdentifier: "com.example.app",
+ expectedAppVersion: "1.0.0"
+ )
+
+ let result = verifier.verify(
+ bundleIdentifier: "com.example.app",
+ versionIdentifier: "2.0.0"
+ )
+
+ #expect(result.isInvalid)
+ if case let .invalid(error) = result {
+ #expect(error is MetaVerificationError)
+ if let metaError = error as? MetaVerificationError {
+ #expect(metaError == .versionIdentifierMismatch)
+ }
+ }
+ }
+
+ @Test
+ func caseSensitiveBundleIdentifierFails() {
+ let verifier = MetaVerifier(
+ expectedBundleIdentifier: "com.example.app",
+ expectedAppVersion: "1.0.0"
+ )
+
+ let result = verifier.verify(
+ bundleIdentifier: "com.Example.App",
+ versionIdentifier: "1.0.0"
+ )
+
+ #expect(result.isInvalid)
+ }
+
+ // MARK: - Provider-Based Initialization
+
+ @Test
+ func providerBasedInitializerWorks() {
+ nonisolated(unsafe) var currentVersion = "1.0.0"
+ nonisolated(unsafe) var currentBundleId = "com.example.app"
+
+ let verifier = MetaVerifier(
+ appVersionProvider: { currentVersion },
+ bundleIdentifierProvider: { currentBundleId }
+ )
+
+ let result1 = verifier.verify(
+ bundleIdentifier: "com.example.app",
+ versionIdentifier: "1.0.0"
+ )
+ #expect(result1.isValid)
+
+ // Change expected values to verify providers return dynamic values
+ currentVersion = "2.0.0"
+ currentBundleId = "com.different.app"
+
+ let result2 = verifier.verify(
+ bundleIdentifier: "com.example.app",
+ versionIdentifier: "1.0.0"
+ )
+ #expect(result2.isInvalid)
+ }
+}
diff --git a/Tests/Validation/Verifiers/SignatureVerifierTests.swift b/Tests/Validation/Verifiers/SignatureVerifierTests.swift
new file mode 100644
index 0000000..90459f1
--- /dev/null
+++ b/Tests/Validation/Verifiers/SignatureVerifierTests.swift
@@ -0,0 +1,198 @@
+import Foundation
+import SwiftASN1
+import Testing
+import X509
+
+@testable import TPInAppReceipt
+
+// MARK: - Shared Test Cases
+
+enum SignatureVerifierTestCases {
+ static func validSignature(
+ using verify: (AppReceipt) -> TPInAppReceipt.VerificationResult
+ ) throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let result = verify(pkcs7)
+ #expect(result.isValid)
+ }
+
+ static func corruptedSignatureFails(
+ using verify: (_ signedData: Data, _ signature: Data) -> TPInAppReceipt.VerificationResult
+ ) throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ var corrupted = pkcs7.signature
+ if corrupted.count > 10 {
+ corrupted[5] ^= 0xFF
+ corrupted[10] ^= 0xFF
+ }
+ let result = verify(pkcs7.digestData, corrupted)
+ #expect(result.isInvalid)
+ }
+
+ static func corruptedSignedDataFails(
+ using verify: (_ signedData: Data, _ signature: Data) -> TPInAppReceipt.VerificationResult
+ ) throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ var corrupted = pkcs7.digestData
+ if corrupted.count > 10 {
+ corrupted[5] ^= 0xFF
+ corrupted[10] ^= 0xFF
+ }
+ let result = verify(corrupted, pkcs7.signature)
+ #expect(result.isInvalid)
+ }
+
+ static func emptySignatureFails(
+ using verify: (_ signedData: Data, _ signature: Data) -> TPInAppReceipt.VerificationResult
+ ) throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let result = verify(pkcs7.digestData, Data())
+ #expect(result.isInvalid)
+ }
+
+ static func emptySignedDataFails(
+ using verify: (_ signedData: Data, _ signature: Data) -> TPInAppReceipt.VerificationResult
+ ) throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let result = verify(Data(), pkcs7.signature)
+ #expect(result.isInvalid)
+ }
+}
+
+// MARK: - X509 SignatureVerifier
+
+@Suite("SignatureVerifier")
+struct SignatureVerifierTests {
+ let verifier = SignatureVerifier()
+
+ @Test
+ func validSignature() throws {
+ try SignatureVerifierTestCases.validSignature { receipt in
+ verifier.verify(receipt)
+ }
+ }
+
+ @Test
+ func corruptedSignatureFails() throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ try SignatureVerifierTestCases.corruptedSignatureFails { signedData, signature in
+ verifier.verify(
+ key: pkcs7.publicKey!,
+ algorithm: pkcs7.digestAlgorithm,
+ signedData: signedData,
+ signature: signature
+ )
+ }
+ }
+
+ @Test
+ func corruptedSignedDataFails() throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ try SignatureVerifierTestCases.corruptedSignedDataFails { signedData, signature in
+ verifier.verify(
+ key: pkcs7.publicKey!,
+ algorithm: pkcs7.digestAlgorithm,
+ signedData: signedData,
+ signature: signature
+ )
+ }
+ }
+
+ @Test
+ func emptySignatureFails() throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ try SignatureVerifierTestCases.emptySignatureFails { signedData, signature in
+ verifier.verify(
+ key: pkcs7.publicKey!,
+ algorithm: pkcs7.digestAlgorithm,
+ signedData: signedData,
+ signature: signature
+ )
+ }
+ }
+
+ @Test
+ func emptySignedDataFails() throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ try SignatureVerifierTestCases.emptySignedDataFails { signedData, signature in
+ verifier.verify(
+ key: pkcs7.publicKey!,
+ algorithm: pkcs7.digestAlgorithm,
+ signedData: signedData,
+ signature: signature
+ )
+ }
+ }
+}
+
+// MARK: - Security Framework SignatureVerifier
+
+#if !os(Linux)
+@Suite("SecSignatureVerifier")
+struct SecSignatureVerifierTests {
+ let verifier = SecSignatureVerifier()
+
+ @Test
+ func validSignature() throws {
+ try SignatureVerifierTestCases.validSignature { receipt in
+ verifier.verify(receipt)
+ }
+ }
+
+ @Test
+ func corruptedSignatureFails() throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let keyData = try Data(pkcs7.publicKey!.intoBytes())
+ try SignatureVerifierTestCases.corruptedSignatureFails { signedData, signature in
+ verifier.verify(
+ key: keyData,
+ algorithm: pkcs7.digestAlgorithm,
+ signedData: signedData,
+ signature: signature
+ )
+ }
+ }
+
+ @Test
+ func corruptedSignedDataFails() throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let keyData = try Data(pkcs7.publicKey!.intoBytes())
+ try SignatureVerifierTestCases.corruptedSignedDataFails { signedData, signature in
+ verifier.verify(
+ key: keyData,
+ algorithm: pkcs7.digestAlgorithm,
+ signedData: signedData,
+ signature: signature
+ )
+ }
+ }
+
+ @Test
+ func emptySignatureFails() throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let keyData = try Data(pkcs7.publicKey!.intoBytes())
+ try SignatureVerifierTestCases.emptySignatureFails { signedData, signature in
+ verifier.verify(
+ key: keyData,
+ algorithm: pkcs7.digestAlgorithm,
+ signedData: signedData,
+ signature: signature
+ )
+ }
+ }
+
+ @Test
+ func emptySignedDataFails() throws {
+ let pkcs7 = try TestingUtility.parseReceipt("Assets/receipt-from-known-device")
+ let keyData = try Data(pkcs7.publicKey!.intoBytes())
+ try SignatureVerifierTestCases.emptySignedDataFails { signedData, signature in
+ verifier.verify(
+ key: keyData,
+ algorithm: pkcs7.digestAlgorithm,
+ signedData: signedData,
+ signature: signature
+ )
+ }
+ }
+}
+#endif