Compare commits

...

7 Commits

Author SHA1 Message Date
Hannes Oud f8dae53736 Merge pull request #48 from IdeasOnCanvas/enhancement/modernizeForSwift41
Remove Sourcery and update README with new references
2018-04-24 11:43:57 +02:00
Hannes Oud 5c90e58864 Update README, add links to AppStoreReceiptChecker 2018-04-16 18:22:13 +02:00
Hannes Oud df47710990 Remove sourcery from README 2018-04-16 18:12:30 +02:00
Hannes Oud 9b03d4b850 Remove sourcery from .swiftlint.yml 2018-04-16 18:08:42 +02:00
Hannes Oud 06b43589d6 Use two char indentation in swiftlint run phase, add SRCROOT as input to prepare for new build system 2018-04-16 18:06:23 +02:00
Hannes Oud 1722f04928 Remove vertical whitespaces 2018-04-16 18:06:23 +02:00
Hannes Oud 16ef7de4bf Remove Sourcery 2018-04-16 17:42:45 +02:00
10 changed files with 14 additions and 199 deletions
-7
View File
@@ -1,7 +0,0 @@
sources:
- AppReceiptValidator
- Sourcery/Protocols
templates:
- Sourcery/Templates
output:
Sourcery/Generated
+1 -1
View File
@@ -4,7 +4,7 @@ disabled_rules:
- nesting
- todo
excluded:
- Sourcery/Generated
- excluded_dir_example
file_length:
warning: 500
large_tuple:
@@ -22,7 +22,6 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
} catch {
XCTFail("Unexpectedly failed parsing a receipt \(error)")
}
}
func testNonMindNodeReceiptParsingWithMultipleInAppPurchases() {
@@ -10,10 +10,6 @@
D1239FFF1F6A7B5000D0421E /* AppleIncRootCertificate.cer in Resources */ = {isa = PBXBuildFile; fileRef = D19095C41F601DEA0095729B /* AppleIncRootCertificate.cer */; };
D123A0001F6A7CCF00D0421E /* AppleIncRootCertificate.cer in Resources */ = {isa = PBXBuildFile; fileRef = D19095C41F601DEA0095729B /* AppleIncRootCertificate.cer */; };
D13E5B7D20331B9B001880F0 /* DropAcceptingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13E5B7C20331B9B001880F0 /* DropAcceptingTextView.swift */; };
D14FA7261F61350F00545540 /* AutoEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14FA71F1F6134CF00545540 /* AutoEquatable.swift */; };
D14FA7271F61351000545540 /* AutoEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14FA71F1F6134CF00545540 /* AutoEquatable.swift */; };
D14FA7291F61351400545540 /* AutoEquatable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14FA71E1F6134CF00545540 /* AutoEquatable.generated.swift */; };
D14FA72A1F61351500545540 /* AutoEquatable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14FA71E1F6134CF00545540 /* AutoEquatable.generated.swift */; };
D14FA7321F61476700545540 /* mac_mindnode_rebought_receipt in Resources */ = {isa = PBXBuildFile; fileRef = D14FA7311F61472400545540 /* mac_mindnode_rebought_receipt */; };
D14FA7331F61476800545540 /* mac_mindnode_rebought_receipt in Resources */ = {isa = PBXBuildFile; fileRef = D14FA7311F61472400545540 /* mac_mindnode_rebought_receipt */; };
D14FA7381F6181C700545540 /* OpenSSLWrappers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14FA7371F6181C700545540 /* OpenSSLWrappers.swift */; };
@@ -280,9 +276,6 @@
/* Begin PBXFileReference section */
D13E5B7C20331B9B001880F0 /* DropAcceptingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropAcceptingTextView.swift; sourceTree = "<group>"; };
D14FA71E1F6134CF00545540 /* AutoEquatable.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoEquatable.generated.swift; sourceTree = "<group>"; };
D14FA71F1F6134CF00545540 /* AutoEquatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoEquatable.swift; sourceTree = "<group>"; };
D14FA7221F6134CF00545540 /* AutoEquatable.stencil */ = {isa = PBXFileReference; lastKnownFileType = text; path = AutoEquatable.stencil; sourceTree = "<group>"; };
D14FA72E1F6143C400545540 /* Date+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Convenience.swift"; sourceTree = "<group>"; };
D14FA7311F61472400545540 /* mac_mindnode_rebought_receipt */ = {isa = PBXFileReference; lastKnownFileType = file; path = mac_mindnode_rebought_receipt; sourceTree = "<group>"; };
D14FA7371F6181C700545540 /* OpenSSLWrappers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSLWrappers.swift; sourceTree = "<group>"; };
@@ -542,40 +535,6 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
D14FA71B1F6134CF00545540 /* Sourcery */ = {
isa = PBXGroup;
children = (
D14FA7231F6134EC00545540 /* Protocols */,
D14FA71D1F6134CF00545540 /* Generated */,
D14FA7201F6134CF00545540 /* Templates */,
);
path = Sourcery;
sourceTree = "<group>";
};
D14FA71D1F6134CF00545540 /* Generated */ = {
isa = PBXGroup;
children = (
D14FA71E1F6134CF00545540 /* AutoEquatable.generated.swift */,
);
path = Generated;
sourceTree = "<group>";
};
D14FA7201F6134CF00545540 /* Templates */ = {
isa = PBXGroup;
children = (
D14FA7221F6134CF00545540 /* AutoEquatable.stencil */,
);
path = Templates;
sourceTree = "<group>";
};
D14FA7231F6134EC00545540 /* Protocols */ = {
isa = PBXGroup;
children = (
D14FA71F1F6134CF00545540 /* AutoEquatable.swift */,
);
path = Protocols;
sourceTree = "<group>";
};
D14FA7341F614A9C00545540 /* OpenSSL */ = {
isa = PBXGroup;
children = (
@@ -851,7 +810,6 @@
D1D6F4921F5D67E600E86FE1 = {
isa = PBXGroup;
children = (
D14FA71B1F6134CF00545540 /* Sourcery */,
D1D6F4FC1F5D696800E86FE1 /* AppReceiptValidator */,
D1D6F4E51F5D691400E86FE1 /* AppReceiptValidator Demo iOS */,
D19095821F6000A40095729B /* AppReceiptValidator Demo macOS */,
@@ -1387,13 +1345,14 @@
files = (
);
inputPaths = (
"$(SRCROOT)",
);
name = Swiftlint;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
};
D1D6F52E1F5D872B00E86FE1 /* Swiftlint */ = {
isa = PBXShellScriptBuildPhase;
@@ -1401,13 +1360,14 @@
files = (
);
inputPaths = (
"$(SRCROOT)",
);
name = Swiftlint;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
};
/* End PBXShellScriptBuildPhase section */
@@ -1454,8 +1414,6 @@
D1D6F53F1F5D89D000E86FE1 /* AppReceiptValidator.swift in Sources */,
D1FE34401F604F540029576B /* AppReceiptValidator+Parameters.swift in Sources */,
D19095C31F6019FC0095729B /* DeviceIdentifier+installedDeviceIdentifier_iOS.swift in Sources */,
D14FA7271F61351000545540 /* AutoEquatable.swift in Sources */,
D14FA7291F61351400545540 /* AutoEquatable.generated.swift in Sources */,
D1DFC5DA20037B8400C7B99B /* KnownOrUnknown.swift in Sources */,
D1FE343D1F604F020029576B /* Receipt.swift in Sources */,
D14FA73B1F618B0100545540 /* ASN1Helpers.swift in Sources */,
@@ -1473,11 +1431,9 @@
D1FE34411F604F540029576B /* AppReceiptValidator+Parameters.swift in Sources */,
D19095C01F60158B0095729B /* DeviceIdentifier+installedDeviceIdentifier_macOS.swift in Sources */,
D1FE343E1F604F020029576B /* Receipt.swift in Sources */,
D14FA72A1F61351500545540 /* AutoEquatable.generated.swift in Sources */,
D1DFC5DB20037B8400C7B99B /* KnownOrUnknown.swift in Sources */,
D14FA73C1F618B0100545540 /* ASN1Helpers.swift in Sources */,
D14FA7391F6181D000545540 /* OpenSSLWrappers.swift in Sources */,
D14FA7261F61350F00545540 /* AutoEquatable.swift in Sources */,
D19095BD1F6004D10095729B /* pkcs7_union_accessors.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -336,7 +336,6 @@ private extension AppReceiptValidator {
case expirationDate = 21
}
/// See Receipt.swift for details and a link to Apple reference
enum KnownInAppPurchaseAttribute: Int32 {
case quantity = 1701
@@ -11,7 +11,7 @@ import Foundation
/// Receipts are made up of a number of fields. This represents all fields that are available locally when parsing a receipt file in ASN.1 form.
///
/// See [Apple Reference](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html)
public struct Receipt {
public struct Receipt: Equatable {
/// The apps bundle identifier. This 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.
@@ -70,10 +70,6 @@ public struct Receipt {
public init() {}
}
// MARK: - Equatable
extension Receipt: AutoEquatable {}
// MARK: - CustomStringConvertible
extension Receipt: CustomStringConvertible, CustomDebugStringConvertible {
@@ -115,7 +111,7 @@ extension Receipt: CustomStringConvertible, CustomDebugStringConvertible {
/// - Subscription Auto Renew Status
/// - Subscription Auto Renew Preference
/// - Subscription Price Consent Status
public struct InAppPurchaseReceipt {
public struct InAppPurchaseReceipt: Equatable {
/// The number of items purchased. ASN.1 Field Type 1701.
/// This value corresponds to the quantity property of the `SKPayment` object stored in the transactions payment property.
@@ -189,10 +185,6 @@ public struct InAppPurchaseReceipt {
public init() {}
}
// MARK: - Equatable
extension InAppPurchaseReceipt: AutoEquatable {}
// MARK: - CustomStringConvertible
extension InAppPurchaseReceipt: CustomStringConvertible, CustomDebugStringConvertible {
@@ -1,56 +0,0 @@
// Generated using Sourcery 0.10.0 https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
// swiftlint:disable file_length
private func compareOptionals<T>(lhs: T?, rhs: T?, compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool {
switch (lhs, rhs) {
case let (lValue?, rValue?):
return compare(lValue, rValue)
case (nil, nil):
return true
default:
return false
}
}
private func compareArrays<T>(lhs: [T], rhs: [T], compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool {
guard lhs.count == rhs.count else { return false }
for (idx, lhsItem) in lhs.enumerated() {
guard compare(lhsItem, rhs[idx]) else { return false }
}
return true
}
// MARK: - AutoEquatable for classes, protocols, structs
// MARK: - InAppPurchaseReceipt AutoEquatable
extension InAppPurchaseReceipt: Equatable {}
public func == (lhs: InAppPurchaseReceipt, rhs: InAppPurchaseReceipt) -> Bool {
guard compareOptionals(lhs: lhs.quantity, rhs: rhs.quantity, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.productIdentifier, rhs: rhs.productIdentifier, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.transactionIdentifier, rhs: rhs.transactionIdentifier, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.originalTransactionIdentifier, rhs: rhs.originalTransactionIdentifier, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.purchaseDate, rhs: rhs.purchaseDate, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.originalPurchaseDate, rhs: rhs.originalPurchaseDate, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.subscriptionExpirationDate, rhs: rhs.subscriptionExpirationDate, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.cancellationDate, rhs: rhs.cancellationDate, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.webOrderLineItemId, rhs: rhs.webOrderLineItemId, compare: ==) else { return false }
return true
}
// MARK: - Receipt AutoEquatable
extension Receipt: Equatable {}
public func == (lhs: Receipt, rhs: Receipt) -> Bool {
guard compareOptionals(lhs: lhs.bundleIdentifier, rhs: rhs.bundleIdentifier, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.bundleIdData, rhs: rhs.bundleIdData, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.appVersion, rhs: rhs.appVersion, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.opaqueValue, rhs: rhs.opaqueValue, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.sha1Hash, rhs: rhs.sha1Hash, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.originalAppVersion, rhs: rhs.originalAppVersion, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.receiptCreationDate, rhs: rhs.receiptCreationDate, compare: ==) else { return false }
guard compareOptionals(lhs: lhs.expirationDate, rhs: rhs.expirationDate, compare: ==) else { return false }
guard lhs.inAppPurchaseReceipts == rhs.inAppPurchaseReceipts else { return false }
return true
}
// MARK: - AutoEquatable for Enums
@@ -1,8 +0,0 @@
//
// AutoEquatable.swift
//
//
// Created by Hannes Oud on 07.09.17.
//
protocol AutoEquatable {}
@@ -1,62 +0,0 @@
// swiftlint:disable file_length
fileprivate func compareOptionals<T>(lhs: T?, rhs: T?, compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool {
switch (lhs, rhs) {
case let (lValue?, rValue?):
return compare(lValue, rValue)
case (nil, nil):
return true
default:
return false
}
}
fileprivate func compareArrays<T>(lhs: [T], rhs: [T], compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool {
guard lhs.count == rhs.count else { return false }
for (idx, lhsItem) in lhs.enumerated() {
guard compare(lhsItem, rhs[idx]) else { return false }
}
return true
}
{% macro compareVariables variables %}
{% for variable in variables where variable.readAccess != "private" and variable.readAccess != "fileprivate" %}{% if not variable.annotations.skipEquality %}guard {% if not variable.isOptional %}{% if not variable.annotations.arrayEquality %}lhs.{{ variable.name }} == rhs.{{ variable.name }}{% else %}compareArrays(lhs: lhs.{{ variable.name }}, rhs: rhs.{{ variable.name }}, compare: ==){% endif %}{% else %}compareOptionals(lhs: lhs.{{ variable.name }}, rhs: rhs.{{ variable.name }}, compare: ==){% endif %} else { return false }{% endif %}
{% endfor %}
{% endmacro %}
// MARK: - AutoEquatable for classes, protocols, structs
{% for type in types.implementing.AutoEquatable|!enum %}
// MARK: - {{ type.name }} AutoEquatable
{% if not type.kind == "protocol" %}extension {{ type.name }}: Equatable {}{% endif %}
{% if type.supertype.based.Equatable or type.supertype.implements.AutoEquatable %}THIS WONT COMPILE, WE DONT SUPPORT INHERITANCE for AutoEquatable{% endif %}
{{ type.accessLevel }} func == (lhs: {{ type.name }}, rhs: {{ type.name }}) -> Bool {
{% if not type.kind == "protocol" %}
{% call compareVariables type.storedVariables %}
{% else %}
{% call compareVariables type.allVariables %}
{% endif %}
return true
}
{% endfor %}
// MARK: - AutoEquatable for Enums
{% for type in types.implementing.AutoEquatable|enum %}
// MARK: - {{ type.name }} AutoEquatable
extension {{ type.name }}: Equatable {}
{{ type.accessLevel }} func == (lhs: {{ type.name }}, rhs: {{ type.name }}) -> Bool {
switch (lhs, rhs) {
{% for case in type.cases %}
{% if case.hasAssociatedValue %}case (.{{ case.name }}(let lhs), .{{ case.name }}(let rhs)):{% else %}case (.{{ case.name }}, .{{ case.name }}):{% endif %}
{% ifnot case.hasAssociatedValue %}return true{% else %}
{% if case.associatedValues.count == 1 %}
return lhs == rhs
{% else %}
{% for associated in case.associatedValues %}if lhs.{{ associated.externalName }} != rhs.{{ associated.externalName }} { return false }
{% endfor %}return true
{% endif %}
{% endif %}
{% endfor %}
{% if type.cases.count > 1 %}default: return false{% endif %}
}
}
{% endfor %}
+7 -5
View File
@@ -73,7 +73,7 @@ Receipt(
)
```
**Receipt** is *Equatable*, thanks [Sourcery](https://github.com/krzysztofzablocki/Sourcery), so you can do comparisons in Unit Tests.
**Receipt** is *Equatable*, so you can do comparisons in Unit Tests.
There are also some opt-in unofficial attributes, but this is experimental and should not be used in production.
### Validating a receipt's signature and hash
@@ -150,19 +150,20 @@ If you have no receipt (happens in development builds) or your receipt is invali
### AppReceiptValidator Uses OpenSSL
OpenSSL is used for pkcs7 container parsing and signature validation, and also for parsing the ASN1 payload of the pkcs7, which contains the receipts attributes.
OpenSSL is used for PKCS#7 container parsing and signature validation, and also for parsing the ASN1 payload of the PKCS#7, which contains the receipts attributes.
### Other Options
##### Alternatives to PKCS7 of OpenSSL
##### Alternatives to PKCS#7 of OpenSSL
- `Security.framework` - `CMSDecoder` for PKCS7 interaction *only available on macOS*
- `BoringSSL` instead of OpenSSL, Pod, *only available on iOS (?)*
- `Security.framework` - `CMSDecoder` for PKCS#7 interaction *only available on macOS*, [AppStoreReceiptChecker](https://github.com/delicious-monster/AppStoreReceiptChecker) uses this.
- `BoringSSL` instead of OpenSSL, seems included as frameworks in modern iOS and macOS, but not officially supported?
##### Alternatives to ASN1 of OpenSSL
- [decoding-asn1-der-sequences-in-swift](http://nspasteboard.com/2016/10/23/decoding-asn1-der-sequences-in-swift/) implemented [here](https://gist.github.com/Jugale/2daaec0715d4f6d7347534d42bfa7110)
- [Asn1Parser.swift](https://github.com/TakeScoop/SwiftyRSA/blob/03250be7319d8c54159234e5258ead395ea4de4c/SwiftyRSA/Asn1Parser.swift)
- [AppStoreReceiptChecker](https://github.com/delicious-monster/AppStoreReceiptChecker)
##### Validation Server to Server
An app can send its receipt file to a backend from where Apples receipt API can be called. See Resources.
@@ -187,6 +188,7 @@ Advantages doing it locally:
- [nsomar about Module Maps 1](http://nsomar.com/project-and-private-headers-in-a-swift-and-objective-c-framework/)
- [nsomar about Module Maps 2](http://nsomar.com/modular-framework-creating-and-using-them/)
- [SwiftyStoreKit](https://github.com/bizz84/SwiftyStoreKit)
- [AppStoreReceiptChecker](https://github.com/delicious-monster/AppStoreReceiptChecker) - macOS, uses CMSDecoder and a Swift ASN1 Implementation
## Updating Apple Root Certificate
For convenience, AppReceiptValidator contains a copy of apples root certificate to validate the signature against. If uncomfortable with this, you can specify your own by changing the parameters like this: