Compare commits

..

1 Commits

Author SHA1 Message Date
Hannes Oud 5f6ce04f2b WIP EXPERIMENTALLY validate certificate chain from receipt against root cert 2021-12-16 18:28:29 +01:00
4 changed files with 60 additions and 49 deletions
@@ -7,10 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
80AB3E2F276CA8E100C4AB61 /* AppReceiptValidator in Frameworks */ = {isa = PBXBuildFile; productRef = 80AB3E2E276CA8E100C4AB61 /* AppReceiptValidator */; };
80AB3E30276CA8E100C4AB61 /* AppReceiptValidator in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 80AB3E2E276CA8E100C4AB61 /* AppReceiptValidator */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
80AB3E32276CA8E500C4AB61 /* AppReceiptValidator in Frameworks */ = {isa = PBXBuildFile; productRef = 80AB3E31276CA8E500C4AB61 /* AppReceiptValidator */; };
80AB3E33276CA8E500C4AB61 /* AppReceiptValidator in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 80AB3E31276CA8E500C4AB61 /* AppReceiptValidator */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
D1150C78274BA3AD007FA109 /* AppReceiptValidator in Frameworks */ = {isa = PBXBuildFile; productRef = D1150C77274BA3AD007FA109 /* AppReceiptValidator */; };
D1150C79274BA3AD007FA109 /* AppReceiptValidator in Embed Frameworks */ = {isa = PBXBuildFile; productRef = D1150C77274BA3AD007FA109 /* AppReceiptValidator */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
D1150C7B274BA3B1007FA109 /* AppReceiptValidator in Frameworks */ = {isa = PBXBuildFile; productRef = D1150C7A274BA3B1007FA109 /* AppReceiptValidator */; };
D1150C7C274BA3B1007FA109 /* AppReceiptValidator in Embed Frameworks */ = {isa = PBXBuildFile; productRef = D1150C7A274BA3B1007FA109 /* AppReceiptValidator */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
D13E5B7D20331B9B001880F0 /* DropAcceptingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13E5B7C20331B9B001880F0 /* DropAcceptingTextView.swift */; };
D19095841F6000A40095729B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19095831F6000A40095729B /* AppDelegate.swift */; };
D19095861F6000A40095729B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19095851F6000A40095729B /* ViewController.swift */; };
@@ -30,7 +30,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
80AB3E33276CA8E500C4AB61 /* AppReceiptValidator in Embed Frameworks */,
D1150C7C274BA3B1007FA109 /* AppReceiptValidator in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -41,7 +41,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
80AB3E30276CA8E100C4AB61 /* AppReceiptValidator in Embed Frameworks */,
D1150C79274BA3AD007FA109 /* AppReceiptValidator in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -72,7 +72,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
80AB3E32276CA8E500C4AB61 /* AppReceiptValidator in Frameworks */,
D1150C7B274BA3B1007FA109 /* AppReceiptValidator in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -80,7 +80,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
80AB3E2F276CA8E100C4AB61 /* AppReceiptValidator in Frameworks */,
D1150C78274BA3AD007FA109 /* AppReceiptValidator in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -160,7 +160,7 @@
);
name = "AppReceiptValidator Demo macOS";
packageProductDependencies = (
80AB3E31276CA8E500C4AB61 /* AppReceiptValidator */,
D1150C7A274BA3B1007FA109 /* AppReceiptValidator */,
);
productName = "AppReceiptValidator Demo macOS";
productReference = D19095811F6000A40095729B /* AppReceiptValidator Demo macOS.app */;
@@ -182,7 +182,7 @@
);
name = "AppReceiptValidator Demo iOS";
packageProductDependencies = (
80AB3E2E276CA8E100C4AB61 /* AppReceiptValidator */,
D1150C77274BA3AD007FA109 /* AppReceiptValidator */,
);
productName = "Demo iOS";
productReference = D1D6F4E41F5D691400E86FE1 /* AppReceiptValidator Demo iOS.app */;
@@ -224,6 +224,7 @@
);
mainGroup = D1D6F4921F5D67E600E86FE1;
packageReferences = (
D1150C73274BA2C8007FA109 /* XCRemoteSwiftPackageReference "AppReceiptValidator" */,
);
productRefGroup = D1D6F49C1F5D67E600E86FE1 /* Products */;
projectDirPath = "";
@@ -583,13 +584,26 @@
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
D1150C73274BA2C8007FA109 /* XCRemoteSwiftPackageReference "AppReceiptValidator" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/IdeasOnCanvas/AppReceiptValidator";
requirement = {
branch = main;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
80AB3E2E276CA8E100C4AB61 /* AppReceiptValidator */ = {
D1150C77274BA3AD007FA109 /* AppReceiptValidator */ = {
isa = XCSwiftPackageProductDependency;
package = D1150C73274BA2C8007FA109 /* XCRemoteSwiftPackageReference "AppReceiptValidator" */;
productName = AppReceiptValidator;
};
80AB3E31276CA8E500C4AB61 /* AppReceiptValidator */ = {
D1150C7A274BA3B1007FA109 /* AppReceiptValidator */ = {
isa = XCSwiftPackageProductDependency;
package = D1150C73274BA2C8007FA109 /* XCRemoteSwiftPackageReference "AppReceiptValidator" */;
productName = AppReceiptValidator;
};
/* End XCSwiftPackageProductDependency section */
@@ -7,7 +7,7 @@
"state": {
"branch": null,
"revision": "6f36ef23becd7f9266ef6b026af4798996a1a8be",
"version": "1.8.1"
"version": null
}
},
{
@@ -154,8 +154,34 @@ private extension AppReceiptValidator {
guard let signatureData = signature.signatureData else { throw Error.receiptNotSigned }
guard let receiptData = pkcs7.mainBlock.findOid(.pkcs7data)?.parent?.sub?.last?.sub(0)?.rawValue else { throw Error.receiptNotSigned }
let rootCert = pkcs7.certificates[0]
try self.verifyAuthenticity(x509Certificate: rootCert, receiptData: receiptData, signatureData: signatureData)
let rootCert1 = pkcs7.certificates[0]
let rootCert2: X509Certificate = try .init(data: appleRootCertificateData)
do {
func validateChain() {
let rootCert = SecCertificateCreateWithData(nil, appleRootCertificateData as CFData)
let receiptCerts = pkcs7.certificatesData.compactMap { SecCertificateCreateWithData(nil, $0 as CFData) }
let policy = SecPolicyCreateBasicX509()
var optionalTrust: SecTrust?
let status = SecTrustCreateWithCertificates([rootCert] as AnyObject,
policy,
&optionalTrust)
if status == errSecSuccess {
let trust = optionalTrust! // Safe to force unwrap now
SecTrustSetAnchorCertificates(trust, [receiptCerts] as CFArray)
var error: CFError?
print("Veryfing result: ", SecTrustEvaluateWithError(trust, &error))
print(error)
}
else {
print("error")
}
}
validateChain()
}
try self.verifyAuthenticity(x509Certificate: rootCert2, receiptData: receiptData, signatureData: signatureData)
}
func verifyAuthenticity(x509Certificate: X509Certificate, receiptData: Data, signatureData: Data) throws {
@@ -164,6 +190,10 @@ private extension AppReceiptValidator {
guard let key = x509Certificate.publicKey?.secKey,
let algorithm = x509Certificate.publicKey?.secAlgorithm else { throw Error.receiptSignatureInvalid }
//
//
// guard SecTrustCreateWithCertificates(<#T##certificates: CFTypeRef##CFTypeRef#>, <#T##policies: CFTypeRef?##CFTypeRef?#>, <#T##trust: UnsafeMutablePointer<SecTrust?>##UnsafeMutablePointer<SecTrust?>#>)
//
var verifyError: Unmanaged<CFError>?
guard SecKeyVerifySignature(key, algorithm, receiptData as CFData, signatureData as CFData, &verifyError),
verifyError == nil else {
@@ -212,7 +242,7 @@ private extension AppReceiptValidator {
let items = receiptBlock.sub else { return .init(entries: []) }
let entries: [UnofficialReceipt.Entry] = items.compactMap { item in
guard let fieldType = (item.sub(0)?.value as? Data)?.uint64ValueOrZero, // provisioning fieldtype pops up here as empty Data, so we use uint64ValueOrZero
guard let fieldType = (item.sub(0)?.value as? Data)?.uint64Value,
KnownReceiptAttribute(rawValue: fieldType) == nil else { return nil }
let fieldValueString = item.sub(2)?.asString
@@ -398,15 +428,3 @@ extension X509PublicKey {
}
#endif
}
// MARK: - Data + Unofficial Receipt FieldTypes
private extension Data {
/// Decoded as by ASN1Decoder's Data.uint64Value extension, or as 0 if empty
var uint64ValueOrZero: UInt64? {
if self.isEmpty { return 0 }
return self.uint64Value
}
}
@@ -247,27 +247,6 @@ class AppReceiptValidatorTests: XCTestCase {
XCTAssertEqual(receipt, expected)
}
func testUnofficialProvisioningTypes() throws {
do {
guard let data = assertB64TestAsset(filename: "mindnode_ios_michaelsandbox_receipt1.b64") else { return }
let result = try receiptValidator.parseUnofficialReceipt(origin: .data(data))
XCTAssertEqual(result.unofficialReceipt.provisioningType, .known(value: .productionSandbox))
}
do {
guard let data = assertB64TestAsset(filename: "mindnode_ios_michaelsandbox_receipt2.b64") else { return }
let result = try receiptValidator.parseUnofficialReceipt(origin: .data(data))
XCTAssertEqual(result.unofficialReceipt.provisioningType, .known(value: .productionSandbox))
}
do {
guard let data = assertB64TestAsset(filename: "hannes_mac_mindnode_receipt.b64") else { return }
let result = try receiptValidator.parseUnofficialReceipt(origin: .data(data))
XCTAssertEqual(result.unofficialReceipt.provisioningType, .known(value: .production))
}
}
func testMindNodeiOSSandBoxReceipt2ParsingAndValidation() {
guard let data = assertB64TestAsset(filename: "mindnode_ios_michaelsandbox_receipt2.b64") else { return }