Skip to content

Commit

Permalink
Some changes to improve licensing
Browse files Browse the repository at this point in the history
  • Loading branch information
javiersantos committed Mar 12, 2017
1 parent 7c51ddf commit e7d4756
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,24 @@
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;

/**
* Client library for Google Play license verifications. <p> The LicenseChecker is configured via a
* Client library for Google Play license verifications. <p> The LibraryChecker is configured via a
* {@link Policy} which contains the logic to determine whether a user should have access to the
* application. For example, the Policy can define a threshold for allowable number of server or
* client failures before the library reports the user as not having access. <p> Must also provide
* the Base64-encoded RSA public key associated with your developer account. The public key is
* obtainable from the publisher site.
*/
@SuppressLint({"SimpleDateFormat", "HardwareIds"})
public class LicenseChecker implements ServiceConnection {
private static final String TAG = "LicenseChecker";
public class LibraryChecker implements ServiceConnection {
private static final String TAG = "LibraryChecker";

private static final String KEY_FACTORY_ALGORITHM = "RSA";

Expand All @@ -71,8 +72,8 @@ public class LicenseChecker implements ServiceConnection {
private final Policy mPolicy;
private final String mPackageName;
private final String mVersionCode;
private final Set<LicenseValidator> mChecksInProgress = new HashSet<>();
private final Queue<LicenseValidator> mPendingChecks = new LinkedList<>();
private final Set<LibraryValidator> mChecksInProgress = new HashSet<>();
private final Queue<LibraryValidator> mPendingChecks = new LinkedList<>();
private ILicensingService mService;
private PublicKey mPublicKey;
/**
Expand All @@ -87,7 +88,7 @@ public class LicenseChecker implements ServiceConnection {
* @param encodedPublicKey Base64-encoded RSA public key
* @throws IllegalArgumentException if encodedPublicKey is invalid
*/
public LicenseChecker(Context context, Policy policy, String encodedPublicKey) {
public LibraryChecker(Context context, Policy policy, String encodedPublicKey) {
mContext = context;
mPolicy = policy;
mPublicKey = generatePublicKey(encodedPublicKey);
Expand Down Expand Up @@ -144,14 +145,14 @@ private static String getVersionCode(Context context, String packageName) {
* recommend obfuscating the string that is passed into bindService using another method of your
* own devising. <p> source string: "com.android.vending.licensing.ILicensingService" <p>
*/
public synchronized void checkAccess(LicenseCheckerCallback callback) {
public synchronized void checkAccess(LibraryCheckerCallback callback) {
// If we have a valid recent LICENSED response, we can skip asking
// Market.
if (mPolicy.allowAccess()) {
Log.i(TAG, "Using cached license response");
callback.allow(Policy.LICENSED);
} else {
LicenseValidator validator = new LicenseValidator(mPolicy, new NullDeviceLimiter(),
LibraryValidator validator = new LibraryValidator(mPolicy, new NullDeviceLimiter(),
callback, generateNonce(), mPackageName, mVersionCode);

if (mService == null) {
Expand Down Expand Up @@ -194,7 +195,7 @@ public synchronized void checkAccess(LicenseCheckerCallback callback) {
handleServiceConnectionError(validator);
}
} catch (SecurityException e) {
callback.applicationError(LicenseCheckerCallback.ERROR_MISSING_PERMISSION);
callback.applicationError(LibraryCheckerCallback.ERROR_MISSING_PERMISSION);
} catch (Base64DecoderException e) {
e.printStackTrace();
}
Expand All @@ -206,7 +207,7 @@ public synchronized void checkAccess(LicenseCheckerCallback callback) {
}

private void runChecks() {
LicenseValidator validator;
LibraryValidator validator;
while ((validator = mPendingChecks.poll()) != null) {
try {
Log.i(TAG, "Calling checkLicense on service for " + validator.getPackageName());
Expand All @@ -221,7 +222,7 @@ private void runChecks() {
}
}

private synchronized void finishCheck(LicenseValidator validator) {
private synchronized void finishCheck(LibraryValidator validator) {
mChecksInProgress.remove(validator);
if (mChecksInProgress.isEmpty()) {
cleanupService();
Expand All @@ -245,7 +246,7 @@ public synchronized void onServiceDisconnected(ComponentName name) {
* Generates policy response for service connection errors, as a result of disconnections or
* timeouts.
*/
private synchronized void handleServiceConnectionError(LicenseValidator validator) {
private synchronized void handleServiceConnectionError(LibraryValidator validator) {
mPolicy.processServerResponse(Policy.RETRY, null);

if (mPolicy.allowAccess()) {
Expand Down Expand Up @@ -293,10 +294,10 @@ private class ResultListener extends ILicenseResultListener.Stub {
private static final int ERROR_CONTACTING_SERVER = 0x101;
private static final int ERROR_INVALID_PACKAGE_NAME = 0x102;
private static final int ERROR_NON_MATCHING_UID = 0x103;
private final LicenseValidator mValidator;
private final LibraryValidator mValidator;
private Runnable mOnTimeout;

public ResultListener(LicenseValidator validator) {
public ResultListener(LibraryValidator validator) {
mValidator = validator;
mOnTimeout = new Runnable() {
public void run() {
Expand All @@ -318,7 +319,7 @@ public void run() {
// Make sure it hasn't already timed out.
if (mChecksInProgress.contains(mValidator)) {
clearTimeout();
mValidator.verify(mPublicKey, responseCode, signedData, signature);
mValidator.check(mPublicKey, responseCode, signedData, Calendar.getInstance(), signature);
finishCheck(mValidator);
}
if (DEBUG_LICENSE_ERROR) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* the policy, while in most cases Policy.NOT_LICENSED will call dontAllow and Policy.LICENSED will
* Allow.
*/
public interface LicenseCheckerCallback {
public interface LibraryCheckerCallback {

/**
* Application error codes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Calendar;

/**
* Contains data related to a licensing request and methods to verify and process the response.
* Contains data related to a licensing request and methods to check and process the response.
*/
class LicenseValidator {
private static final String TAG = "LicenseValidator";
class LibraryValidator {
private static final String TAG = "LibraryValidator";

// Server response codes.
private static final int LICENSED = 0x0;
Expand All @@ -47,13 +48,13 @@ class LicenseValidator {
private static final int ERROR_NON_MATCHING_UID = 0x103;
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
private final Policy mPolicy;
private final LicenseCheckerCallback mCallback;
private final LibraryCheckerCallback mCallback;
private final int mNonce;
private final String mPackageName;
private final String mVersionCode;
private final DeviceLimiter mDeviceLimiter;

LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback,
LibraryValidator(Policy policy, DeviceLimiter deviceLimiter, LibraryCheckerCallback callback,
int nonce, String packageName, String versionCode) {
mPolicy = policy;
mDeviceLimiter = deviceLimiter;
Expand All @@ -63,7 +64,7 @@ class LicenseValidator {
mVersionCode = versionCode;
}

public LicenseCheckerCallback getCallback() {
public LibraryCheckerCallback getCallback() {
return mCallback;
}

Expand All @@ -83,11 +84,14 @@ public String getPackageName() {
* @param signedData signed data from server
* @param signature server signature
*/
public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) {
public void check(PublicKey publicKey, int responseCode, String signedData, Calendar calendar, String signature) {
String userId = null;
// Skip signature check for unsuccessful requests
ResponseData data = null;
if (responseCode == LICENSED || responseCode == NOT_LICENSED ||

if (calendar == null) {
handleInvalidResponse();
} else if (responseCode == LICENSED || responseCode == NOT_LICENSED ||
responseCode == LICENSED_OLD_KEY) {
// Verify signature.
try {
Expand All @@ -111,7 +115,7 @@ public void verify(PublicKey publicKey, int responseCode, String signedData, Str
// This can't happen on an Android compatible device.
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY);
handleApplicationError(LibraryCheckerCallback.ERROR_INVALID_PUBLIC_KEY);
return;
} catch (Base64DecoderException e) {
Log.e(TAG, "Could not Base64-decode signature.");
Expand Down Expand Up @@ -183,13 +187,13 @@ public void verify(PublicKey publicKey, int responseCode, String signedData, Str
handleResponse(Policy.RETRY, data);
break;
case ERROR_INVALID_PACKAGE_NAME:
handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME);
handleApplicationError(LibraryCheckerCallback.ERROR_INVALID_PACKAGE_NAME);
break;
case ERROR_NON_MATCHING_UID:
handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID);
handleApplicationError(LibraryCheckerCallback.ERROR_NON_MATCHING_UID);
break;
case ERROR_NOT_MARKET_MANAGED:
handleApplicationError(LicenseCheckerCallback.ERROR_NOT_MARKET_MANAGED);
handleApplicationError(LibraryCheckerCallback.ERROR_NOT_MARKET_MANAGED);
break;
default:
Log.e(TAG, "Unknown response code for license check.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package com.github.javiersantos.licensing;

/**
* Policy used by {@link LicenseChecker} to determine whether a user should have access to the
* Policy used by {@link LibraryChecker} to determine whether a user should have access to the
* application.
*/
public interface Policy {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.github.javiersantos.piracychecker;

import com.github.javiersantos.licensing.AESObfuscator;
import com.github.javiersantos.licensing.LicenseChecker;
import com.github.javiersantos.licensing.LicenseCheckerCallback;
import com.github.javiersantos.licensing.LibraryChecker;
import com.github.javiersantos.licensing.LibraryCheckerCallback;
import com.github.javiersantos.licensing.ServerManagedPolicy;

import android.annotation.SuppressLint;
Expand Down Expand Up @@ -92,7 +92,7 @@ public void dontAllow(PiracyCheckerError error) {
}

protected void verify(final PiracyCheckerCallback verifyCallback) {
// Library will verify first the non-LVL methods since LVL is asynchronous and could take
// Library will check first the non-LVL methods since LVL is asynchronous and could take
// some seconds to give a result
if (!verifySigningCertificate()) {
verifyCallback.dontAllow(PiracyCheckerError.SIGNATURE_NOT_VALID);
Expand All @@ -102,10 +102,10 @@ protected void verify(final PiracyCheckerCallback verifyCallback) {
if (enableLVL) {
String deviceId = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ANDROID_ID);
LicenseChecker licenseChecker = new LicenseChecker(context, new
LibraryChecker libraryChecker = new LibraryChecker(context, new
ServerManagedPolicy(context, new AESObfuscator(LibraryUtils.SALT, context
.getPackageName(), deviceId)), licenseBase64);
licenseChecker.checkAccess(new LicenseCheckerCallback() {
libraryChecker.checkAccess(new LibraryCheckerCallback() {
@Override
public void allow(int reason) {
verifyCallback.allow();
Expand Down

1 comment on commit e7d4756

@javiersantos
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ref #13

Please sign in to comment.