Android In app billing - remove Security class dependency - android

I'm using the In App Billing sample app to add this feature to my application.
After I finished adding it to my app, and tested all working, I noticed the comment in this Security class:
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.
As Google suggests, I do the purchase verification on the server side so I really don't need the Security class in my project.
The problem is, I can't figure out how to remove the BillingService class dependency in the Security class.
I started by deleting the Security class and following the errors in the BillingService and most places it's being used I can remove easily, except in one place:
private void purchaseStateChanged(int startId, String signedData, String signature) {
ArrayList<Security.VerifiedPurchase> purchases;
purchases = Security.verifyPurchase(signedData, signature);
if (purchases == null) {
return;
}
ArrayList<String> notifyList = new ArrayList<String>();
for (VerifiedPurchase vp : purchases) {
if (vp.notificationId != null) {
notifyList.add(vp.notificationId);
}
ResponseHandler.purchaseResponse(this, vp.purchaseState, vp.productId,
vp.orderId, vp.purchaseTime, vp.developerPayload);
}
if (!notifyList.isEmpty()) {
String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
confirmNotifications(startId, notifyIds);
}
}
Would love if someone can share his/hers purchaseStateChanged method (based on the in app billing sample app) without the use of the Security class.

So here's what I did. First the calls to BillingService occur on the applications main thread, so you need to issue your server calls in a background thread. I chose to finish up processing on the main thread, since I wasn't sure what impact calling methods like 'confirmNotifications' on a background thread might have.
I created a callback interface VerifyTransactionCompletion which could be dispatched back to the main thread after the remote call completed.
I keep around the Security class and have it manage the call to the server now, instead of what it originally performed in the sample. So when you see the call to Security, that's where I call out to my server and perform signature validation.
/**
* Callback interface to <em>finish</em> processing a transaction once the remote
* servers have processed it.
*/
public interface VerifyTransactionCompletion {
public void transactionVerified(List<Security.VerifiedPurchase> purchases);
}
private void purchaseStateChanged(final int startId, String signedData, String signature) {
// verifyPurchase issues remote call to server (in a background thread), then
// calls transactionVerified on the main thread to continue processing.
Security.verifyPurchase(signedData, signature, new VerifyTransactionCompletion() {
#Override
public void transactionVerified(List<VerifiedPurchase> purchases) {
if (purchases == null) {
return;
}
ArrayList<String> notifyList = new ArrayList<String>();
for (VerifiedPurchase vp : purchases) {
if (vp.notificationId != null) {
notifyList.add(vp.notificationId);
}
ResponseHandler.purchaseResponse(BillingService.this, vp.purchaseState, vp.productId,
vp.orderId, vp.purchaseTime, vp.developerPayload);
}
if (!notifyList.isEmpty()) {
String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
confirmNotifications(startId, notifyIds);
}
}
});
}

Related

Using queryPurchases in an AysncTask by passsing the billing client to that class - not returning expected result

I am implementing in-app billing. I am able to do all I want so far until it comes to checking the users previous purchases. In my first activity in onCreate I create an instance of my BillingManger class which handles all of the in-app billing.
I then call a getPurchases to retrieve a PurchaseResult object in the after this in the onCreate (& onResume) methods:
//Creating instance....
billingManager = new BillingManager(this, this.snackCoordinatorLayout);
//Checking purchases....
billingManager.getPurchases(this);
The getPurchases method creates a instance of a class which extends AsyncTask to query the purchases asynchronously. It takes a listener for the call-back and a BillingClient member variable (this is where I think I may be going wrong - explained why at the end.):
//Querying purchases with AsyncTask
void getPurchases(PurchaseAsyncResponse listener) {
CheckPurchasesAsync check = new CheckPurchasesAsync(listener, this.mBillingClient);
check.execute();
}
Here's the AsyncTask class. This returns a PurchaseResult via an interface callback which it successfully does.
public class CheckPurchasesAsync extends AsyncTask<Void, Void, Purchase.PurchasesResult> {
private PurchaseAsyncResponse listener;
private BillingClient mBillingClient;
CheckPurchasesAsync(PurchaseAsyncResponse listener, BillingClient billingClient) {
this.listener = listener;
this.mBillingClient = billingClient;
}
#Override
protected Purchase.PurchasesResult doInBackground(Void... voids) {
return mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
}
#Override
protected void onPostExecute(Purchase.PurchasesResult purchasesResult) {
listener.purchaseAsyncResponse(purchasesResult);
}
Now in the call-back is where I release something isn't working as I wanted.
#Override
public void purchaseAsyncResponse(Purchase.PurchasesResult purchase) {
if (purchase.getPurchasesList() != null) {
getMessage("mPurchaes NotNull");
//Only need to get the first element - element 0 because their is only one product for now.
if (purchase.getPurchasesList().get(0).getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
getMessage("Unlocking everything");
unlockEveryThing();
}
} else {
getMessage("mPurchases Null ASYNC");
}
}
purchase.getPurchasesList() is null when it shouldn't be so I get a snackbar message called in the resulting else statement.
As part of debugging I called queryPurchase in another method. I have a button that allows the user to purchase premium features for the first time (that is suppose to disappear if I didn't have the problem I am asking about.) I have conditions set-up that show a message for different billing response codes. So as it stands now after buying for the first time, any time there after I display a message saying item already owned.
However, I quired the purchases in this block and was able to get back a successful PurchaseResult object and I was then able to use the getPurchaseList method to find out details about the purchase and it wasn't null like it is in the instance I explained about above. Here is that code snippet:
else if (responseCode.getResponseCode() == BillingResponseCode.ITEM_ALREADY_OWNED) {
getMessage("Item already owned");
Purchase.PurchasesResult PR = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
getMessage(String.valueOf(PR.getPurchasesList().get(0).getSku()));
}
Is the problem that I am sending the mBillingClient to the AysncTask class and it loses its reference and all its meaning?
If so, how could I fix this?
So in the end up I needed to use the startConnection method of the BillingClient. After this all worked as desired.

Message specific user in a Phonegap/Cordova app using SignalR 2

I am attempting to create a real-time communication capability for a Phonegap/Cordova app. I am using SignalR 2 to handle the communication.
The thing I am struggling with is getting a message to a particular user. Every single example out there shows saving Context.User.Identity.Name, which is useless to me because the remote site's User.Identity context is not shared by my phonegap app.
In essence, I am not authenticating a user in the traditional sense, so I need another way of linking the SignalR connectionID with the username I pass along.
Taken from the official ASP.NET signalr Examples, I have the following code which overrides the OnConnected event. Unfortunately it takes no parameters and expects User.Identity to be not null:
public override Task OnConnected()
{
using (var db = new UserContext())
{
// Retrieve user.
var user = db.Users
.Include(u => u.Rooms)
.SingleOrDefault(u => u.UserName == Context.User.Identity.Name);
// If user does not exist in database, must add.
if (user == null)
{
user = new User()
{
UserName = Context.User.Identity.Name
};
db.Users.Add(user);
db.SaveChanges();
}
else
{
// Add to each assigned group.
foreach (var item in user.Rooms)
{
Groups.Add(Context.ConnectionId, item.RoomName);
}
}
}
return base.OnConnected();
}
Now, maybe what I'd need is to have a version of this method that takes a string as a parameter and then I'd use that as my user identifier.
But how to go about that?
You need to create a new IUserIdProvider for the user and use dependency injection to register your provider and use it.
public interface IUserIdProvider
{
string GetUserId(IRequest request);
}
Register your provider with Global Host
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => new MyIdProvider());
Usage:
public class MyHub : Hub
{
public void Send(string userId, string message)
{
Clients.User(userId).send(message);
}
}
Taken from: http://www.asp.net/signalr/overview/guide-to-the-api/mapping-users-to-connections#IUserIdProvider

Protecting in-app purchases from Freedom Hack

I've throughtoutly searched this site as well as others for answers and found no actual one.
My question is what exactly does the Freedom Hack (which allows users to get in-app purchases without paying) do. That is, what part of the process is altered. I've found this list of applications for which the hack works, and some of the entries there are dated to this month, meaning that it hasn't been completely fixed yet. The responses I've seen were "verify the application in your server", but if the hack, for example, alters the Java.Security's signature verification function, so it always returns true, then adding my own signature in the server wouldn't help much.
I don't know if the author still follow this topic or not. But I spent sometime to find out (googling) the way how freedom work and how to prevent it (until they update the way freedom work) in my project and it works. My implementation is really simple and you don't need to verify by sending request to server (which affect the performance and take more effort to implement it).
The current implementation of freedom is that it will replace (redirect) all the method calls of java.security.Signature.verify(byte[]) to a freedom's jni method which in turn just simply always return true (or 1).
Take a look at java.security.Signature.verify(byte[]):
public final boolean verify(byte[] signature) throws SignatureException {
if (state != VERIFY) {
throw new SignatureException("Signature object is not initialized properly");
}
return engineVerify(signature);
}
Here the engineVerify method is an abstract protected method which is first defined in java.security.SignatureSpi(Signature extends SignatureSpi).
OK, that enough, because I can't believe java.security.Signature.verify(byte[]) method anymore, I would use engineVerify method directly. To do that, we need to use reflection. Modify the verify method of IABUtil/Security from:
public static boolean verify(PublicKey publicKey, String signedData, String 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 (...) {
...
}
return false;
}
To:
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
Method verify = java.security.SignatureSpi.class.getDeclaredMethod("engineVerify", byte[].class);
verify.setAccessible(true);
Object returnValue = verify.invoke(sig, Base64.decode(signature));
if (!(Boolean)returnValue) {
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (...) {
...
}
return false;
}
That is simple but it works with the current implementation of freedom until they update its algorithm in the future.
then adding my own signature in the server wouldn't help much.
That is not correct, the signature that "Freedom" uses is invalid and the order id is also invalid.
What I did to ensure that my Application is safe is:
Send isPurchaseValid(myPurchase.getSignature(), myPurchase.getOriginalJson()) to my server to verify over there and it works with real purchases but freedom fails everytime.
On the server I check if the signature matches
If it does match I contact "Google APIs Google Play Android Developer API > androidpublisher.inapppurchases.get" to verify that the Purchase exists and that returns my developer payload.
I then use the developer payload to make sure that this purchase is for this specific user and not some other user and this user is sending me his data.
P.S. The developer payload is a String you set before the purchase is made from your android app, it should be something unique to your user.
It maybe a lot of work but It ensure that no one will buy your stuff with freedom and succeed.
The only thing that I am unable to do is not let freedom have an affect on my application, for example the folks in Path did something I don't know what which made Freedom have no effect what so ever!!!!
I'm using something like this, I know it's not a good solution compared to a remote server check for your signature. I'm checking if Freedom app is installed, if so I'm not opening my app.
#Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
if(isHackerAppIsntalled())
finish();
}
private boolean isHackerAppInstalled() {
final PackageManager pm = getApplication().getPackageManager();
List<ApplicationInfo> packages = pm
.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo packageInfo : packages) {
String packageName = packageInfo.packageName;
if (packageName.contains("cc.madkite.freedom")
|| packageName.contains("madkite.freedom")) {
return true;
}
}
return false;
}

Login flow for Gigya in mobile app with custom login UI

I'm developing an Android app using Gigya to allow people to register using Facebook and Twitter; in parallel another developer is doing the same thing in iOS. We want to implement custom login UI.
The standard method uses Gigya's own UI and is documented here:
http://developers.gigya.com/035_Mobile_SDKs/020_Android#Logging_in_the_User
Beneath, it simply suggests:
If you wish to implement the graphic design by yourself, use the login method instead.
The standard login method calls a dedicated post-login callback with an onLogin(...) method and all subsequent flows are described as stemming from this event. The other login method calls a standard onGSResponse(...) callback; it's not clear how the response can be used to construct a user so I've set up my implementation to call socialize.getUserInfo. Attempts to call either method have resulted in lots of unusual errors.
As per the Gigya instructions I'm starting up with
mGSAPI = new GSAPI(GIGYA_APP_KEY, this);
mGSAPI.setAPIDomain("eu1.gigya.com");
in onCreate(...) (where GIGYA_APP_KEY is a value copied from our console). I'm calling setAPIDomain because we were getting an invalid data center error (albeit with a 500001 code, not a 301001 code!), which this has fixed.
Facebook login goes through the login flow as I'd expect and then comes back with error 400093 (which the docs tell me is an invalid API parameter, and has the message " Missing parameter: client_id").
Twitter login comes back with 206002, " Account Pending Verification", which seems to make sense; I then call
mGSAPI.sendRequest(
"getUserInfo",
null, //parameters
true, //use HTTPS
this, //the callback
null //a context object
);
and this gives me the error:
Missing required parameter: No secret or signature were provided. Request could not be verified.
The documentation for socialize.getUserInfo suggest a UID is required for web apps, but not for native ones. It mentions no other mandatory fields. I am a bit stuck ... shouldn't the GSAPI object be handling verification, as it's initialized with the API key?
I can give you some direction at a very high level for integrating GIGYA. (Code below is not verbatim) Hopefully it is somewhat helpful.
For a private Android app I had created a Manager object (GigyaManager) that maintained a singleton instance of the GSAPI object.
This singleton GigyaManager was initialized in my application object:
public static GigyaManager getInstance(String apiKey, Context context) {
mGSAPI = new GSAPI(apiKey, context);
}
My GigyaManager class also had a wrapper method for handling the login w/social services:
public void loginWithSocialService(GigyaSocialProvider provider, GSResponseListener listener) throws Exception {
// did the user attempt a social login, and bail out on the registration
// phase?
if (GigyaManager.getInstance().getGSAPI().getSession() != null) {
logout();
}
GSObject providerArgs = new GSObject();
providerArgs.put(GigyaManager.GIGYA_ARG_PROVIDER, provider.name().toLowerCase());
mGSAPI.login(providerArgs, listener, null);
}
This was fired from an onClick listener in a fragment that contained a "login" button:
GigyaManager.getInstance("appKey", getActivity()).loginWithSocialService(GigyaSocialProvider.FACEBOOK, this);
That fragment had to implement GSResponseListener that has the callbacks to deal with whether the login was successful or not:
#Override
public void onGSResponse(String method, GSResponse response, Object context) {
if (!method.equalsIgnoreCase("login") || response.getErrorCode() != 0) {
return;
}
GIGYAResponseWrapper resp = new GIGYAResponseWrapper(response.getResponseText());
// user is attached to login provider?
if (resp.isIsAttached()) {
// start some sort of loader or asynctask to get information about user account
// connected to GIGYA social login
Bundle args = new Bundle();
args.putString(ARG_UID, resp.getUid());
args.putString(ARG_UID_SIGNATURE, resp.getUidSignature());
args.putString(ARG_SIGNATURE_TIMESTAMP, resp.getSignatureTimestamp());
args.putString(ARG_SOCIAL_NICKNAME, resp.getNickname());
} else {
// login success, but this social account is not associated with anything in GIGYA
}
}

Custom Account authenticator. Cleanup after account is removed from device

Is there a way to get some kind of notification/broadcast/etc. when a custom account is removed from "Accounts & sync settings"?
The application I have can facilitate multiple users on a device (this is for a corporate use) and uses a single SQLite database. Say I create multiple users for my application on a device and populate database with data that is relevant only to those two users. My problem here is that if one of the user is removed from "Accounts & sync settings" I have no way to cleanup database and/or some external files on SD card.
I could duplicate user information in a redundant table and compare it with registered accounts and then removing user data from the database if user information in the table and Account[] array from AccountManager does not match. Feels dirty to me.
You have two options:
You can use the addOnAccountsUpdatedListener method of AccountManager to add a listener in the onCreate method of an Activity or Service -- make sure you remove the listener in your onDestroy method (i.e. do NOT use this in an endlessly running service) or the Context used to retrieve the AccountManager will never be garbage collected
The AccountsService will broadcast an intent with the action AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION every time an account is added, removed or changed which you can add a receiver for.
I didn't see a lot of examples on how people implement account cleanup, so I thought I would post my solution (really a variation of the accepted answer).
public class AccountAuthenticatorService extends Service {
private AccountManager _accountManager;
private Account[] _currentAccounts;
private OnAccountsUpdateListener _accountsUpdateListener = new OnAccountsUpdateListener() {
#Override
public void onAccountsUpdated(Account[] accounts) {
// NOTE: this is every account on the device (you may want to filter by type)
if(_currentAccounts == null){
_currentAccounts = accounts;
return;
}
for(Account currentAccount : _currentAccounts) {
boolean accountExists = false;
for (Account account : accounts) {
if(account.equals(currentAccount)){
accountExists = true;
break;
}
}
if(!accountExists){
// Take actions to clean up. Maybe send intent on Local Broadcast reciever
}
}
}
};
public AccountAuthenticatorService() {
}
#Override
public void onCreate() {
super.onCreate();
_accountManager = AccountManager.get(this);
// set to true so we get the current list of accounts right away.
_accountManager.addOnAccountsUpdatedListener(_accountsUpdateListener, new Handler(), true);
}
#Override
public void onDestroy() {
super.onDestroy();
_accountManager.removeOnAccountsUpdatedListener(_accountsUpdateListener);
}
#Override
public IBinder onBind(Intent intent) {
AccountAuthenticator authenticator = new AccountAuthenticator(this);
return authenticator.getIBinder();
}
}

Categories

Resources