diff --git a/astrid/src/com/todoroo/astrid/billing/Security.java b/astrid/src/com/todoroo/astrid/billing/Security.java index f39045605..d230dbab1 100644 --- a/astrid/src/com/todoroo/astrid/billing/Security.java +++ b/astrid/src/com/todoroo/astrid/billing/Security.java @@ -2,35 +2,17 @@ package com.todoroo.astrid.billing; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.SecureRandom; -import java.security.Signature; -import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.HashSet; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.text.TextUtils; -import android.util.Log; - import com.todoroo.astrid.billing.BillingConstants.PurchaseState; + /** - * 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. + * This is a stub class + * @author Sam */ @SuppressWarnings("nls") public class Security { @@ -40,15 +22,6 @@ public class Security { private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; private static final SecureRandom RANDOM = new SecureRandom(); - /** - * This keeps track of the nonces that we generated and sent to the - * server. We need to keep track of these until we get back the purchase - * state and send a confirmation message back to Android Market. If we are - * killed and lose this list of nonces, it is not fatal. Android Market will - * send us a new "notify" message and we will re-generate a new nonce. - * This has to be "static" so that the {@link BillingReceiver} can - * check if a nonce exists. - */ private static HashSet sKnownNonces = new HashSet(); /** @@ -90,166 +63,19 @@ public class Security { return sKnownNonces.contains(nonce); } - /** - * Verifies that the data was signed with the given signature, and returns - * the list of verified purchases. The data is in JSON format and contains - * a nonce (number used once) that we generated and that was signed - * (as part of the whole data string) with a private key. The data also - * contains the {@link PurchaseState} and product ID of the purchase. - * In the general case, there can be an array of purchase transactions - * because there may be delays in processing the purchase on the backend - * and then several purchases can be batched together. - * @param signedData the signed JSON string (signed, not encrypted) - * @param signature the signature for the data, signed with the private key - */ public static ArrayList verifyPurchase(String signedData, String signature) { - if (signedData == null) { - Log.e(TAG, "data is null"); - return null; - } - if (BillingConstants.DEBUG) { - Log.i(TAG, "signedData: " + signedData); - } - boolean verified = false; - if (!TextUtils.isEmpty(signature)) { - /** - * Compute your public key (that you got from the Android Market publisher site). - * - * Instead of just storing the entire literal string here embedded in the - * program, construct the key at runtime from pieces or - * use bit manipulation (for example, XOR with some other string) to hide - * the actual key. The key itself is not secret information, but we don't - * want to make it easy for an adversary to replace the public key with one - * of their own and then fake messages from the server. - * - * Generally, encryption keys / passwords should only be kept in memory - * long enough to perform the operation they need to perform. - */ - - String base64EncodedPublicKey = constructPublicKey(); - PublicKey key = Security.generatePublicKey(base64EncodedPublicKey); - verified = Security.verify(key, signedData, signature); - if (!verified) { - Log.w(TAG, "signature does not match data."); - return null; - } - } - - JSONObject jObject; - JSONArray jTransactionsArray = null; - int numTransactions = 0; - long nonce = 0L; - try { - jObject = new JSONObject(signedData); - - // The nonce might be null if the user backed out of the buy page. - nonce = jObject.optLong("nonce"); - jTransactionsArray = jObject.optJSONArray("orders"); - if (jTransactionsArray != null) { - numTransactions = jTransactionsArray.length(); - } - } catch (JSONException e) { - return null; - } - - if (!Security.isNonceKnown(nonce)) { - Log.w(TAG, "Nonce not found: " + nonce); - return null; - } - - ArrayList purchases = new ArrayList(); - try { - for (int i = 0; i < numTransactions; i++) { - JSONObject jElement = jTransactionsArray.getJSONObject(i); - int response = jElement.getInt("purchaseState"); - PurchaseState purchaseState = PurchaseState.valueOf(response); - String productId = jElement.getString("productId"); - String packageName = jElement.getString("packageName"); - long purchaseTime = jElement.getLong("purchaseTime"); - String orderId = jElement.optString("orderId", ""); - String notifyId = null; - if (jElement.has("notificationId")) { - notifyId = jElement.getString("notificationId"); - } - String purchaseToken = jElement.optString("purchaseToken"); - String developerPayload = jElement.optString("developerPayload", null); - - // If the purchase state is PURCHASED, then we require a - // verified nonce. - if (purchaseState == PurchaseState.PURCHASED && !verified) { - continue; - } - purchases.add(new VerifiedPurchase(purchaseState, notifyId, productId, - orderId, purchaseTime, developerPayload, purchaseToken)); - } - } catch (JSONException e) { - Log.e(TAG, "JSON exception: ", e); - return null; - } - removeNonce(nonce); - return purchases; + return null; } private static String constructPublicKey() { - return BillingConstants.PUB_KEY_OBFUSCATED.replace(BillingConstants.PUB_KEY_OBFUSCATION_CHAR, - BillingConstants.PUB_KEY_REPLACE_CHAR); + return ""; } - /** - * 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 generatePublicKey(String encodedPublicKey) { - try { - byte[] decodedKey = Base64.decode(encodedPublicKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); - 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); - } catch (Base64DecoderException e) { - Log.e(TAG, "Base64 decoding failed."); - throw new IllegalArgumentException(e); - } + return null; } - /** - * 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 - */ public static boolean verify(PublicKey publicKey, String signedData, String signature) { - if (BillingConstants.DEBUG) { - Log.i(TAG, "signature: " + signature); - } - Signature sig; - try { - sig = Signature.getInstance(SIGNATURE_ALGORITHM); - sig.initVerify(publicKey); - sig.update(signedData.getBytes()); - if (!sig.verify(Base64.decode(signature))) { - Log.e(TAG, "Signature verification failed."); - return false; - } - return true; - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, "NoSuchAlgorithmException."); - } catch (InvalidKeyException e) { - Log.e(TAG, "Invalid key specification."); - } catch (SignatureException e) { - Log.e(TAG, "Signature exception."); - } catch (Base64DecoderException e) { - Log.e(TAG, "Base64 decoding failed."); - } return false; } }