Closed ripperhe closed 1 year ago
I'm also curious about this, it will be nice to learn if/how TPInAppReceipt
is affected.
Looking at the source code, one candidate is verifySignature()
which checks if the signature is valid, as well as the chain of trust (mentioned in Apple's docs).
@ripperhe @phi161 Based on research I did, TPInAppReceipt
is not affected.
Looking into checkSignatureValidity
function we can see that we take an algorithm from the receipt itself:
var umErrorCF: Unmanaged<CFError>? = 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))
}
Nevertheless, I'm planing to come back to the issue on June 20 to double check that everything works well.
Thank you!
Hi @tikhop, this does fail (I checked) but I'm not 100% sure why. verifyHash()
is part of the validation process for isValid
in this library and uses the computedHash
which is SHA1 only. Apple don't mention that part of the verification process changing but it fails for me as it doesn't match the receiptHash
. I tested it by purchasing a product using a StoreKit configuration file on an iOS 17 device (it's the 20th June where I am)
@yusuftor thanks for checking. I was actually curious about this part of validation since it's the only part that explicitly using SHA1, but, as you found as well, there is nothing should be changed based on Apple docs.
If you have a chance, could you please share your receipt?
Hey @yusuftor. I confirm that the hash validation step fails, while the other steps are working well. I have inspected both: the decoded receipt and the raw one and everything looks good.
Hey @ripperhe, @phi161, @yusuftor. I've tried different apps and devices, but the hash verification step continues to fail. I suspect this might be an issue on Apple's end, so I ended up submitting a Technical Support Incident request to Apple.
I'll update this thread as soon as I hear back from the Apple team.
Thank you!
Thanks so much for submitting this to Apple and keeping us updated @tikhop! Hoping they resolve this soon.
In my case, things seem to be working fine, I'm starting to wonder if I'm doing something wrong here! Running this code, does not throw any error:
do {
let receipt = try InAppReceipt() // Returns local receipt
try receipt.validate()
print("valid")
} catch {
print(error)
}
Why does verification fail for you?
@phi161 I do the same what you do, so there is nothing wrong in your code, and it fails when we try to verify the hash of the receipt. If it works for you, then you either still have an old version of the receipt on your device or it's magically got fixed by Apple.
Please, before validating try to refresh your receipt first :
InAppReceipt.refresh { (error) in
if let err = error {
print(err)
} else {
initializeReceipt()
}
}
Just to note:
Here is the commit when the change was implemented in order to support SHA256 version of certificates
@phi161 I have tried to perform my tests on another device with a different Apple ID, but using the same app and it works well whereas my original device still fails. 🤷♂
Hi @tikhop, thank you for taking the time to look into this! Can I help you somehow, to better understand what's going on? Would it help if we ran the same project for example (just changing the app id every time?)
Any update on this from Apple @tikhop?
Hi @yusuftor, not yet - they only asked to provide additional information.
@phi161 Sorry, somehow I missed your comment. I don't think it will help to solve the issue since the problem is definitely on apple side.
Okay thanks for the update. Providing them with a basic sample project with this framework that shows the issue would be useful, if you haven't already done that
@yusuftor yup, I provided everything they need to reproduce the issue: receipt, device uid, the project I use to verify the library and the library itself.
Update, I think I solved this. It's not an Apple issue, it's the way that the guid()
method works. I tried a different receipt validation framework with my own receipt and it worked. Currently for iOS this framework is doing this:
var uuidBytes = UIDevice.current.identifierForVendor?.uuid
return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes))
This is somehow not the right way to do it. The correct way to do it is this:
if let identifierForVendor = UIDevice.current.identifierForVendor {
var rawUUID = identifierForVendor.uuid
let count = MemoryLayout.size(ofValue: rawUUID)
let data = withUnsafePointer(to: &rawUUID) {
Data(bytes: $0, count: count)
}
return data
}
I'm not 100% sure about how other platforms should be changed in regards to this but this is what I've got:
private func guid() -> Data {
#if os(watchOS)
if let identifierForVendor = WKInterfaceDevice.current().identifierForVendor {
var rawUUID = identifierForVendor.uuid
let count = MemoryLayout.size(ofValue: rawUUID)
let data = withUnsafePointer(to: &rawUUID) {
Data(bytes: $0, count: count)
}
return data
}
return Data()
#elseif !targetEnvironment(macCatalyst) && (os(iOS) || os(tvOS))
if let identifierForVendor = UIDevice.current.identifierForVendor {
var rawUUID = identifierForVendor.uuid
let count = MemoryLayout.size(ofValue: rawUUID)
let data = withUnsafePointer(to: &rawUUID) {
Data(bytes: $0, count: count)
}
return data
}
return Data()
#elseif targetEnvironment(macCatalyst) || os(macOS)
if let guid = getMacAddress() {
return guid
} else {
assertionFailure("Failed to retrieve guid")
}
return Data()
#endif
}
@yusuftor Awesome. Thanks for narrowing it down. It works for me now.
The core issue in the example you have provided is that MemoryLayout.size(ofValue: uuidBytes)
returns 17 instead of 16, since uuidBytes
has the following type: Optional<(UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)>
. We can either construct the data as you shown in your example or just unwrap identifierForVendor
first and then use the same method we currently have:
var uuidBytes = UIDevice.current.identifierForVendor!.uuid // Or if let uuidBytes = UIDevice.current.identifierForVendor?.uuid
return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes))
I will apply the patch shortly.
Thank you!
I see, thanks for explaining. I think it’s best not to use force unwrapping wherever possible just incase it causes crashes in production.
@yusuftor I agree, but here we should expect identifierForVendor
and if we don't have it than something really strange is going on.
@yusuftor Unfortunately, hash verification still doesn't work. The code you have provided doesn't match the current implementation where I unwrap the uuid and have the correct Data
. I still think that the issues is on Apple side, since it has been working for years and still work for other devices. Apple, on the other hand, says that it works as expected.
It works for me, what changed between you saying it worked and then saying it didn’t work?
@yusuftor
Your code:
var uuidBytes = UIDevice.current.identifierForVendor?.uuid
return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes))
The code from the library:
var uuidBytes = UIDevice.current.identifierForVendor!.uuid
return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes))
These two snippets works differently and the library uses the second snippet which yields the same result as:
if let identifierForVendor = UIDevice.current.identifierForVendor {
var rawUUID = identifierForVendor.uuid
let count = MemoryLayout.size(ofValue: rawUUID)
let data = withUnsafePointer(to: &rawUUID) {
Data(bytes: $0, count: count)
}
return data
}
In other words, the solution you have proposed does not solve the issue because it produce exactly the same Data
object as the current implementation.
The reason I have changed my mind is due to that misleading snippet you have provided. I thought you copied it from the library.
Ah it seems I was using a version of this framework which I modified a while back to remove the force unwraps and in doing so broke the validation. I assumed that was part of this framework's original code. So the original way actually works for me. I'm not sure why it doesn't for you. Can you upload your sample project here that demonstrates it not working? Want to see if it stops working for me too.
@yusuftor, @phi161. I apologize for confusion, it was my mistake. Everything is actually working as expected. As I mentioned earlier, validation functions properly on one device but not on another. Upon further investigation, I discovered that I have a different sandbox account from the main one I used to download the app from TestFlight. Switching from the sandbox account to my regular account resolved the issue, and now all validation step work flawlessly.
Thanks again.
https://developer.apple.com/news/?id=smofnyhj