Adds purchase local verification

dev
Laurent 7 years ago
parent 7448d73b19
commit cd377ed262
  1. 20
      app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt
  2. 127
      app/src/main/java/net/pokeranalytics/android/util/billing/Security.kt

@ -4,6 +4,9 @@ import android.app.Activity
import android.content.Context import android.content.Context
import com.android.billingclient.api.* import com.android.billingclient.api.*
import net.pokeranalytics.android.ui.activity.IAPProducts import net.pokeranalytics.android.ui.activity.IAPProducts
import timber.log.Timber
import java.io.IOException
/** /**
* the AppGuard object is in charge of contacting the Billing services to retrieve products, * the AppGuard object is in charge of contacting the Billing services to retrieve products,
@ -11,6 +14,8 @@ import net.pokeranalytics.android.ui.activity.IAPProducts
*/ */
object AppGuard : PurchasesUpdatedListener { object AppGuard : PurchasesUpdatedListener {
private val BASE_64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0VuL5bk2w0FNaZIwE3v2857ZxJlo0epIIsVJfVs7Pqh7zE1JN7uGBgZ/r2s5Rn3o0R1Ycqxp832kYg/B0FvlQJ3Ko6IZkoyfJQ3i1zuAcc7NLMxKUMJY8Mc0U6Go2bevjQ54WkvumIdAFWIlMjyuOOFcSZRZr8V7tlq0SYlenkuellQeHIq3V47M/0jlDrEbCFj59hsukN75eGIiafFAxBYO/8L/flkZLik8YyhV1uZTu+KziA0PsbIvXKyN+gCK9UmrscTyM4+hfmRgb74fro67UsEqq2OvmHFUhubPzCZDElOwPeauUDEGeQjJn43iUHZWIcSEVktVB9cFa/0JwIDAQAB"
/** /**
* The Billing Client making requests with Google Billing services * The Billing Client making requests with Google Billing services
*/ */
@ -66,7 +71,6 @@ object AppGuard : PurchasesUpdatedListener {
} else { } else {
this.startConnection(runnable) this.startConnection(runnable)
} }
} }
/** /**
@ -141,10 +145,9 @@ object AppGuard : PurchasesUpdatedListener {
*/ */
private fun handlePurchase(purchase: Purchase) { private fun handlePurchase(purchase: Purchase) {
val token = purchase.purchaseToken // val token = purchase.purchaseToken
val oj = purchase.originalJson
if (this.verifyPurchase(purchase)) { if (this.verifyValidSignature(purchase.originalJson, purchase.signature)) {
when (purchase.sku) { when (purchase.sku) {
IAPProducts.PRO.identifier -> { IAPProducts.PRO.identifier -> {
this.isProUser = true this.isProUser = true
@ -161,8 +164,13 @@ object AppGuard : PurchasesUpdatedListener {
/** /**
* Verifies the validity of a purchase * Verifies the validity of a purchase
*/ */
private fun verifyPurchase(purchase: Purchase): Boolean { private fun verifyValidSignature(signedData: String, signature: String): Boolean {
return true try {
return Security.verifyPurchase(BASE_64_ENCODED_PUBLIC_KEY, signedData, signature)
} catch (e: IOException) {
Timber.d("Got an exception trying to validate a purchase: $e")
return false
}
} }
} }

@ -0,0 +1,127 @@
package net.pokeranalytics.android.util.billing
/*
* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.text.TextUtils
import android.util.Base64
import com.android.billingclient.util.BillingHelper
import java.io.IOException
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.X509EncodedKeySpec
/**
* Security-related methods. For a secure implementation, all of this code should be implemented on
* a server that communicates with the application on the device.
*/
object Security {
private val TAG = "IABUtil/Security"
private val KEY_FACTORY_ALGORITHM = "RSA"
private val SIGNATURE_ALGORITHM = "SHA1withRSA"
/**
* Verifies that the data was signed with the given signature, and returns the verified
* purchase.
* @param base64PublicKey the base64-encoded public key to use for verifying.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
* @throws IOException if encoding algorithm is not supported or key specification
* is invalid
*/
@Throws(IOException::class)
fun verifyPurchase(
base64PublicKey: String, signedData: String,
signature: String
): Boolean {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
|| TextUtils.isEmpty(signature)
) {
BillingHelper.logWarn(TAG, "Purchase verification failed: missing data.")
return false
}
val key = generatePublicKey(base64PublicKey)
return verify(key, signedData, signature)
}
/**
* Generates a PublicKey instance from a string containing the Base64-encoded public key.
*
* @param encodedPublicKey Base64-encoded public key
* @throws IOException if encoding algorithm is not supported or key specification
* is invalid
*/
@Throws(IOException::class)
fun generatePublicKey(encodedPublicKey: String): PublicKey {
try {
val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT)
val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM)
return keyFactory.generatePublic(X509EncodedKeySpec(decodedKey))
} catch (e: NoSuchAlgorithmException) {
// "RSA" is guaranteed to be available.
throw RuntimeException(e)
} catch (e: InvalidKeySpecException) {
val msg = "Invalid key specification: $e"
BillingHelper.logWarn(TAG, msg)
throw IOException(msg)
}
}
/**
* Verifies that the signature from the server matches the computed signature on the data.
* Returns true if the data is correctly signed.
*
* @param publicKey public key associated with the developer account
* @param signedData signed data from server
* @param signature server signature
* @return true if the data and signature match
*/
fun verify(publicKey: PublicKey, signedData: String, signature: String): Boolean {
val signatureBytes: ByteArray
try {
signatureBytes = Base64.decode(signature, Base64.DEFAULT)
} catch (e: IllegalArgumentException) {
BillingHelper.logWarn(TAG, "Base64 decoding failed.")
return false
}
try {
val signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM)
signatureAlgorithm.initVerify(publicKey)
signatureAlgorithm.update(signedData.toByteArray())
if (!signatureAlgorithm.verify(signatureBytes)) {
BillingHelper.logWarn(TAG, "Signature verification failed.")
return false
}
return true
} catch (e: NoSuchAlgorithmException) {
// "RSA" is guaranteed to be available.
throw RuntimeException(e)
} catch (e: InvalidKeyException) {
BillingHelper.logWarn(TAG, "Invalid key specification.")
} catch (e: SignatureException) {
BillingHelper.logWarn(TAG, "Signature exception.")
}
return false
}
}
Loading…
Cancel
Save