2

I'm trying to verify Android in-app purchases from my server using the Google Checkout API. As per this other query (App on Android market - HTTP notifications don't come), I get no callback so I'm using the polling API.

It's working fine, except that I'm getting a 5 or 6 minute delay before the polling receives the notifications about a purchase, even though all the information is already visible to the user logged in to the merchant account in a browser. Checking the API documentation, it implies it could take even longer, as it states "Using the Polling API, you can retrieve all notifications that are less than 180 days old and that are at least 30 minutes old".

Is this delay typical (I'm in the UK)? Is the polling API still the recommended way to verify an Android in-app purchase?

Community
  • 1
  • 1
Marion McKelvie
  • 424
  • 1
  • 5
  • 15
  • Do you want to validate on a server on on Android? – class stacker Apr 15 '13 at 13:09
  • I'm validating from a server, before allowing some content to be downloaded to the Android device. – Marion McKelvie Apr 15 '13 at 13:19
  • Have you considered an approach where you keep the certificate only on the server and have the app forward the (signed) Android Google Play service results to the server for real-time validation? At least that's possible with IAB V3. Although, admittedly, the IAB works without a nonce, so responses could theoretically be re-played. But of course you can check consistency on the server if it's not the buy request but a posession request. For the buy process, there's a payload, which you could specify on the server side. – class stacker Apr 15 '13 at 13:37
  • I saw the reference to performing signature verification on the server in the documentation, but I don't think I understand exactly what can be checked. I have a licence from the developer console for the app which I thought had to be present in the app on the device for a purchase to take place, and I have a certificate for signing the app. Don't these both have to be on the Android device? – Marion McKelvie Apr 15 '13 at 15:18
  • Umm, no. The certificate for signing _must not_ be made public, otherwise people can sign apps in your name. And the key from the developer console _needs not_ be present in the app, because all you're doing with it is checking the integrity of Gopogle Play Service results. Nobody forces you to do that in your app; in fact, it's even relatively risky. You can do it on the server side; you just pass the signed data which your app receives from the Google Play Service to your server and validate it there. You should generate the nonce for the request on the server, too, for maximum safety. – class stacker Apr 15 '13 at 15:25
  • Sorry, I think I may be causing some confusion. I didn't mean the certificate for signing the app was on the device, I meant the app is exported as a signed app using that certificate. I've been using the example Google IABHelper class which requires the developer console licence within the app. Sounds like I need to review what that's doing a bit more! – Marion McKelvie Apr 15 '13 at 18:09

1 Answers1

3

In my opinion, trying to validate GP LVL and/or IAB information via the Google Checkout Polling API on a server is not the best approach. There's a much better option available if you have a server anyway.

As mentioned in the article Securing Android LVL Applications, the best approach is to validate licence information on a trusted server. It goes like this:

  1. Don't use the Google demo code; it is not robust (does not check for all error conditions) and can be replaced even by scripts such as to fake a response (although, if you implement the server-side check as below, that's irrelevant anyway). Use com.android.vending.licensing directly. Don't include your Google Developer Console app key with your app, you don't need it there.
  2. Your App asks your server for a nonce for the ILicensingService.checkLicense() call. Your server supplies a secure random nonce to your app. Your app calls ILicensingService.checkLicense() with that nonce.
  3. The Android GP LVL Servce calls back your app via ILicenseResultListener.verifyLicense(), prodiving signed data and a signature. (Hint: The signed data contains the nonce, so not even a re-play attack is possible here.)
  4. Your app passes the signed data along with the signature to your server.
  5. Your server is the only instance which knows your Google Developer Console app key. It validates the signature against the signed data.
  6. The validation result will contribute to your authentication decision regarding access to server data.
  7. Make sure you do not check the licence too often. Google wants you to obey the validity time stamp provided with the licence response (and they claim it even reflects the 15 minute refund period). Obviously, this would only be safe if you store the validity on the server side and the server allows the app to skip the test in step 2.

With one difference, the same applies to IAB. Unfortunately, IAB V3 does not work with a nonce for getPurchases(). The reason is probably that the IAB Service itself (and not just the Google app-side reference code) uses caching extensively. Still, for purchases, you can pass a developerPayload to com.android.vending.billing.IInAppBillingService.getBuyIntent(), which will be included in the signed data which getPurchases() returns. So as long as you have either no expiration criteria or some kind of implicit (time-based) or server-managed explicit expiration criteria for in-app purchases, the API is still safe enough; the server would then ask the app to consume expired items and it's not even a problem if that fails because the server still knows it and can ask the app to consume the items again and again.

I hope I could shed a bit of light on this topic.

Community
  • 1
  • 1
class stacker
  • 5,357
  • 2
  • 32
  • 65
  • Many thanks, your response has got me to look into the demo code in much more detail! I'm verifying the signature on the server now and I'll add the ILicensingService as you suggest too. Thanks again! – Marion McKelvie Apr 16 '13 at 15:43
  • @MarionMcKelvie, when you send "developer payload" string, what do you use to identify exactly this particular user? I mean device ID is related only to device and can be used just once, when user is buying. But if he will change the device later and will request for previously purchased items, I have to be sure if it is he again (not the hacker with saved data) but in the same time I also should not deny if device id is changed. Google recommends to add user-identifing payload but how should I get some user-related id, just like user-id hash on Windows Phone? – Tertium Jun 15 '13 at 11:29
  • @Tertium If you use IAB, you'll likely initially use LVL to identify the user, and you can use Google's user ID from that, plus some additional info specific to this purchase. Only in this combination, you can be sure that you're not facing a different user who uses a re-play attack. IAB data could _always_ be re-played data if your app is hacked, because, as I said, there's no nonce you can pass the the IAB service; the signed IAB response will forever be the same. – class stacker Jun 16 '13 at 08:47
  • I use LVL (Google lib) but don't know how to obtain Google's user ID from it. Anyway, can I be sure that this ID will be the same after - say - 6-12 months for user using this GoogleID (email) to log in and purchase on Google Play (or chosen in GP app settings as purchase ID)? – Tertium Jun 16 '13 at 09:13
  • @Tertium If you use the Google LVL library, then why do you even bother? A chain is only as strong as its weakest link, and that would be using the Google LVL lib in your case, because its license check can be eliminated by applying a script to your .apk. – class stacker Jun 18 '13 at 06:24
  • No, it's impossible. LVL was fully overwritten, combined with our private NDK-written API and integrated into the project classes deeply. I just wanted to say that I'm using Google LVL technology. But as long as it was done over 1 year ago (and I did not use userID in device limiter) I could not understand what id you're talking about. But thanks, I reread man and in the the very end have found mention about this id, and that it is the same on every device user has (in this app). So this is what I asked - yes, it remains the same! – Tertium Jun 18 '13 at 10:31