// Copyright 2010 Google Inc. All Rights Reserved.
package yuku.easybilling;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* 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. For the sake of simplicity and clarity of this example, this code
* is included here and is executed on the device. If you must verify the
* purchases on the phone, you should obfuscate this code to make it harder for
* an attacker to replace the code with stubs that treat all purchases as
* verified.
*/
public class BillingSecurity {
private static final String TAG = "BillingService";
public static class SignedData {
public long nonce;
public List<Order> orders;
public static class Order {
public String notificationId;
public String orderId;
public String packageName;
public String productId;
public long purchaseTime;
public PurchaseState purchaseState;
public String developerPayload;
}
}
/**
* Verifies that the data was signed with the given signature.
*
* @param inapp_signed_data
* the signed JSON string (signed, not encrypted)
* @param inapp_signature
* the signature for the data, signed with the private key
*/
static boolean verifySignedData(String inapp_signed_data, String inapp_signature) {
if (!TextUtils.isEmpty(inapp_signature) && !TextUtils.isEmpty(inapp_signed_data)) {
return verify(getPublicKey(), inapp_signed_data, inapp_signature);
}
return false;
}
static SignedData decodeSignedData(String inapp_signed_data) {
SignedData res = new SignedData();
JSONObject jObject = null;
try {
jObject = new JSONObject(inapp_signed_data);
} catch (JSONException e) {
Log.e(TAG, "json error decoding: " + jObject, e);
return null;
}
res.nonce = jObject.optLong("nonce");
JSONArray jTransactionsArray = jObject.optJSONArray("orders");
if (jTransactionsArray == null) {
res.orders = new ArrayList<SignedData.Order>(0);
} else {
res.orders = new ArrayList<SignedData.Order>(jTransactionsArray.length());
}
for (int i = 0; i < jTransactionsArray.length(); i++) {
JSONObject jElement = jTransactionsArray.optJSONObject(i);
SignedData.Order order = new SignedData.Order();
order.notificationId = jElement.optString("notificationId");
order.orderId = jElement.optString("orderId");
order.packageName = jElement.optString("packageName");
order.productId = jElement.optString("productId");
order.purchaseTime = jElement.optLong("purchaseTime");
order.purchaseState = PurchaseState.valueOf(jElement.optInt("purchaseState"));
order.developerPayload = jElement.optString("developerPayload");
res.orders.add(order);
}
return res;
}
/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey
* Base64-encoded public key
* @throws IllegalArgumentException
* if encodedPublicKey is invalid
*/
public static PublicKey getPublicKey() {
try {
byte[] decodedKey = Base64.decode(EasyBilling.base64Key, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
Log.e(TAG, "Invalid key specification.");
throw new IllegalArgumentException(e);
}
}
/**
* 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 inapp_signed_data
* signed data from server
* @param inapp_signature
* server signature
* @return true if the data and signature match
*/
public static boolean verify(PublicKey publicKey, String inapp_signed_data, String inapp_signature) {
try {
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(publicKey);
sig.update(inapp_signed_data.getBytes());
if (!sig.verify(Base64.decode(inapp_signature, Base64.DEFAULT))) {
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (Exception e) {
Log.e(TAG, "Verify got exception", e);
return false;
}
}
}