Loading vending-app/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ android { dependencies { implementation project(':fake-signature') implementation project(':play-services-auth') implementation "com.squareup.wire:wire-runtime:$wireVersion" implementation "com.android.volley:volley:$volleyVersion" Loading vending-app/src/main/AndroidManifest.xml +9 −2 Original line number Diff line number Diff line Loading @@ -5,12 +5,19 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <permission android:name="com.android.vending.CHECK_LICENSE" android:protectionLevel="normal" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" android:maxSdkVersion="22" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" android:maxSdkVersion="22" /> <application android:forceQueryable="true" android:icon="@mipmap/ic_app" Loading vending-app/src/main/java/com/android/vending/licensing/LicenseRequest.java +8 −6 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import okio.ByteString; public abstract class LicenseRequest<T> extends Request<T> { private final String xPsRh; private final String auth; private static final String TAG = "FakeLicenseRequest"; private static final int BASE64_FLAGS = Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING; Loading @@ -49,8 +50,9 @@ public abstract class LicenseRequest<T> extends Request<T> { private final Response.Listener<T> successListener; protected LicenseRequest(String url, Response.Listener<T> successListener, Response.ErrorListener errorListener) { protected LicenseRequest(String url, String auth, Response.Listener<T> successListener, Response.ErrorListener errorListener) { super(GET, url, errorListener); this.auth = auth; this.successListener = successListener; Loading Loading @@ -143,7 +145,7 @@ public abstract class LicenseRequest<T> extends Request<T> { public Map<String, String> getHeaders() { return Map.of( "X-PS-RH", xPsRh, "Authorization", "Bearer ya29.[…]]", "Authorization", "Bearer " + auth, "Connection", "Keep-Alive" ); } Loading @@ -162,9 +164,9 @@ public abstract class LicenseRequest<T> extends Request<T> { public static class V1 extends LicenseRequest<V1Container> { public V1(String packageName, int versionCode, long nonce, Response.Listener<V1Container> successListener, Response.ErrorListener errorListener) { public V1(String packageName, String auth, int versionCode, long nonce, Response.Listener<V1Container> successListener, Response.ErrorListener errorListener) { super("https://play-fe.googleapis.com/fdfe/apps/checkLicense?pkgn=" + packageName + "&vc=" + versionCode + "&nnc=" + nonce, successListener, errorListener auth, successListener, errorListener ); } Loading @@ -187,11 +189,11 @@ public abstract class LicenseRequest<T> extends Request<T> { } public static class V2 extends LicenseRequest<String> { public V2(String packageName, int versionCode, Response.Listener<String> successListener, public V2(String packageName, String auth, int versionCode, Response.Listener<String> successListener, Response.ErrorListener errorListener) { super( "https://play-fe.googleapis.com/fdfe/apps/checkLicenseServerFallback?pkgn=" + packageName + "&vc=" + versionCode, successListener, errorListener auth, successListener, errorListener ); } Loading vending-app/src/main/java/com/android/vending/licensing/LicensingService.java +147 −96 Original line number Diff line number Diff line Loading @@ -5,6 +5,12 @@ package com.android.vending.licensing; import static android.accounts.AccountManager.KEY_AUTHTOKEN; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.app.Service; import android.content.Intent; import android.content.pm.PackageInfo; Loading @@ -18,16 +24,19 @@ import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.Volley; import org.microg.gms.auth.AuthConstants; import java.io.IOException; import java.util.concurrent.TimeUnit; public class LicensingService extends Service { private static final String TAG = "FakeLicenseService"; private RequestQueue queue; private AccountManager accountManager; private static final String KEY_V2_RESULT_JWT = "LICENSE_DATA"; private final ILicensingService.Stub mLicenseService = new ILicensingService.Stub() { private static final String AUTH_TOKEN_SCOPE = "oauth2:https://www.googleapis.com/auth/googleplay"; /* Possible response codes for checkLicense v1, from * https://developer.android.com/google/play/licensing/licensing-reference#server-response-codes and Loading Loading @@ -73,6 +82,9 @@ public class LicensingService extends Service { */ private static final int ERROR_NON_MATCHING_UID = 0x103; private final ILicensingService.Stub mLicenseService = new ILicensingService.Stub() { @Override public void checkLicense(long nonce, String packageName, ILicenseResultListener listener) throws RemoteException { Log.v(TAG, "checkLicense(" + nonce + ", " + packageName + ")"); Loading @@ -85,39 +97,50 @@ public class LicensingService extends Service { Log.e(TAG, "an app illegally tried to request v1 licenses for another app (caller: " + getCallingUid() + ")"); listener.verifyLicense(ERROR_NON_MATCHING_UID, null, null); } else { Request request = new LicenseRequest.V1(packageName, versionCode, nonce, data -> { Account[] accounts = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE); if (accounts.length == 0) { Log.e(TAG, "not checking license, as user is not signed in"); } else accountManager.getAuthToken( accounts[0], AUTH_TOKEN_SCOPE, false, future -> { Request request = null; try { request = new LicenseRequest.V1( packageName, future.getResult().getString(KEY_AUTHTOKEN), versionCode, nonce, data -> { if (data != null) { Log.v(TAG, "licenseV1 result was " + data.result + "with signed data " + data.signedData); Log.v(TAG, "licenseV1 result was " + data.result + " with signed data " + data.signedData); try { if (data.result != null) { listener.verifyLicense(data.result, data.signedData, data.signature); } else { listener.verifyLicense(LICENSED, data.signedData, data.signature); } } else { Log.v(TAG, "licenseV1 result was that user has no license"); listener.verifyLicense(NOT_LICENSED, null, null); } } catch (RemoteException e) { Log.e(TAG, "After returning licenseV1 result, remote threw an Exception."); Log.e(TAG, "After telling it the licenseV1 result, remote threw an Exception."); e.printStackTrace(); } } else { Log.v(TAG, "licenseV1 result was that user has no license"); sendError(listener, NOT_LICENSED); } }, error -> { Log.e(TAG, "licenseV1 request failed with " + error.toString()); try { listener.verifyLicense(ERROR_CONTACTING_SERVER, null, null); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV1 had an error when contacting server, remote threw an Exception."); e.printStackTrace(); Log.e(TAG, "Caused after network error:"); error.printStackTrace(); } sendError(listener, ERROR_CONTACTING_SERVER); }); } catch (AuthenticatorException | IOException | OperationCanceledException e) { sendError(listener, ERROR_CONTACTING_SERVER); } request.setShouldCache(false); queue.add(request); }, null); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "an app tried to request v1 licenses for package " + packageName + ", which does not exist"); Loading @@ -138,8 +161,18 @@ public class LicensingService extends Service { Log.e(TAG, "an app illegally tried to request v2 licenses for another app (caller: " + getCallingUid() + ")"); listener.verifyLicense(ERROR_NON_MATCHING_UID, new Bundle()); } else { Request request = new LicenseRequest.V2(packageName, versionCode, jwt -> { Account[] accounts = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE); if (accounts.length == 0) { Log.e(TAG, "not checking license, as user is not signed in"); } else accountManager.getAuthToken( accounts[0], AUTH_TOKEN_SCOPE, false, future -> { try { Bundle result = future.getResult(10, TimeUnit.SECONDS); String auth = result.getString(KEY_AUTHTOKEN); Request request = new LicenseRequest.V2(packageName, auth, versionCode, jwt -> { Log.v(TAG, "LicenseV2 returned JWT license value " + jwt); Bundle bundle = new Bundle(); bundle.putString(KEY_V2_RESULT_JWT, jwt); Loading @@ -151,18 +184,18 @@ public class LicensingService extends Service { } }, error -> { Log.e(TAG, "licenseV2 request failed with " + error.toString()); try { listener.verifyLicense(ERROR_CONTACTING_SERVER, new Bundle()); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV2 had an error when contacting server, remote threw an Exception."); e.printStackTrace(); Log.e(TAG, "Caused after network error:"); error.printStackTrace(); } sendError(listener, ERROR_CONTACTING_SERVER); }); request.setShouldCache(false); queue.add(request); } catch (AuthenticatorException | IOException | OperationCanceledException e) { sendError(listener, ERROR_CONTACTING_SERVER); e.printStackTrace(); } }, null ); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "an app tried to request v1 licenses for package " + packageName + ", which does not exist"); Loading @@ -172,8 +205,26 @@ public class LicensingService extends Service { } }; private static void sendError(ILicenseResultListener listener, int error) { try { listener.verifyLicense(error, null, null); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV1 had an error (" + error + "), remote threw an Exception."); } } private static void sendError(ILicenseV2ResultListener listener, int error) { try { listener.verifyLicense(error, new Bundle()); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV2 had an error (" + error + "), remote threw an Exception."); } } public IBinder onBind(Intent intent) { queue = Volley.newRequestQueue(this); accountManager = AccountManager.get(this); return mLicenseService; } Loading Loading
vending-app/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ android { dependencies { implementation project(':fake-signature') implementation project(':play-services-auth') implementation "com.squareup.wire:wire-runtime:$wireVersion" implementation "com.android.volley:volley:$volleyVersion" Loading
vending-app/src/main/AndroidManifest.xml +9 −2 Original line number Diff line number Diff line Loading @@ -5,12 +5,19 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <permission android:name="com.android.vending.CHECK_LICENSE" android:protectionLevel="normal" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" android:maxSdkVersion="22" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" android:maxSdkVersion="22" /> <application android:forceQueryable="true" android:icon="@mipmap/ic_app" Loading
vending-app/src/main/java/com/android/vending/licensing/LicenseRequest.java +8 −6 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import okio.ByteString; public abstract class LicenseRequest<T> extends Request<T> { private final String xPsRh; private final String auth; private static final String TAG = "FakeLicenseRequest"; private static final int BASE64_FLAGS = Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING; Loading @@ -49,8 +50,9 @@ public abstract class LicenseRequest<T> extends Request<T> { private final Response.Listener<T> successListener; protected LicenseRequest(String url, Response.Listener<T> successListener, Response.ErrorListener errorListener) { protected LicenseRequest(String url, String auth, Response.Listener<T> successListener, Response.ErrorListener errorListener) { super(GET, url, errorListener); this.auth = auth; this.successListener = successListener; Loading Loading @@ -143,7 +145,7 @@ public abstract class LicenseRequest<T> extends Request<T> { public Map<String, String> getHeaders() { return Map.of( "X-PS-RH", xPsRh, "Authorization", "Bearer ya29.[…]]", "Authorization", "Bearer " + auth, "Connection", "Keep-Alive" ); } Loading @@ -162,9 +164,9 @@ public abstract class LicenseRequest<T> extends Request<T> { public static class V1 extends LicenseRequest<V1Container> { public V1(String packageName, int versionCode, long nonce, Response.Listener<V1Container> successListener, Response.ErrorListener errorListener) { public V1(String packageName, String auth, int versionCode, long nonce, Response.Listener<V1Container> successListener, Response.ErrorListener errorListener) { super("https://play-fe.googleapis.com/fdfe/apps/checkLicense?pkgn=" + packageName + "&vc=" + versionCode + "&nnc=" + nonce, successListener, errorListener auth, successListener, errorListener ); } Loading @@ -187,11 +189,11 @@ public abstract class LicenseRequest<T> extends Request<T> { } public static class V2 extends LicenseRequest<String> { public V2(String packageName, int versionCode, Response.Listener<String> successListener, public V2(String packageName, String auth, int versionCode, Response.Listener<String> successListener, Response.ErrorListener errorListener) { super( "https://play-fe.googleapis.com/fdfe/apps/checkLicenseServerFallback?pkgn=" + packageName + "&vc=" + versionCode, successListener, errorListener auth, successListener, errorListener ); } Loading
vending-app/src/main/java/com/android/vending/licensing/LicensingService.java +147 −96 Original line number Diff line number Diff line Loading @@ -5,6 +5,12 @@ package com.android.vending.licensing; import static android.accounts.AccountManager.KEY_AUTHTOKEN; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.app.Service; import android.content.Intent; import android.content.pm.PackageInfo; Loading @@ -18,16 +24,19 @@ import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.Volley; import org.microg.gms.auth.AuthConstants; import java.io.IOException; import java.util.concurrent.TimeUnit; public class LicensingService extends Service { private static final String TAG = "FakeLicenseService"; private RequestQueue queue; private AccountManager accountManager; private static final String KEY_V2_RESULT_JWT = "LICENSE_DATA"; private final ILicensingService.Stub mLicenseService = new ILicensingService.Stub() { private static final String AUTH_TOKEN_SCOPE = "oauth2:https://www.googleapis.com/auth/googleplay"; /* Possible response codes for checkLicense v1, from * https://developer.android.com/google/play/licensing/licensing-reference#server-response-codes and Loading Loading @@ -73,6 +82,9 @@ public class LicensingService extends Service { */ private static final int ERROR_NON_MATCHING_UID = 0x103; private final ILicensingService.Stub mLicenseService = new ILicensingService.Stub() { @Override public void checkLicense(long nonce, String packageName, ILicenseResultListener listener) throws RemoteException { Log.v(TAG, "checkLicense(" + nonce + ", " + packageName + ")"); Loading @@ -85,39 +97,50 @@ public class LicensingService extends Service { Log.e(TAG, "an app illegally tried to request v1 licenses for another app (caller: " + getCallingUid() + ")"); listener.verifyLicense(ERROR_NON_MATCHING_UID, null, null); } else { Request request = new LicenseRequest.V1(packageName, versionCode, nonce, data -> { Account[] accounts = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE); if (accounts.length == 0) { Log.e(TAG, "not checking license, as user is not signed in"); } else accountManager.getAuthToken( accounts[0], AUTH_TOKEN_SCOPE, false, future -> { Request request = null; try { request = new LicenseRequest.V1( packageName, future.getResult().getString(KEY_AUTHTOKEN), versionCode, nonce, data -> { if (data != null) { Log.v(TAG, "licenseV1 result was " + data.result + "with signed data " + data.signedData); Log.v(TAG, "licenseV1 result was " + data.result + " with signed data " + data.signedData); try { if (data.result != null) { listener.verifyLicense(data.result, data.signedData, data.signature); } else { listener.verifyLicense(LICENSED, data.signedData, data.signature); } } else { Log.v(TAG, "licenseV1 result was that user has no license"); listener.verifyLicense(NOT_LICENSED, null, null); } } catch (RemoteException e) { Log.e(TAG, "After returning licenseV1 result, remote threw an Exception."); Log.e(TAG, "After telling it the licenseV1 result, remote threw an Exception."); e.printStackTrace(); } } else { Log.v(TAG, "licenseV1 result was that user has no license"); sendError(listener, NOT_LICENSED); } }, error -> { Log.e(TAG, "licenseV1 request failed with " + error.toString()); try { listener.verifyLicense(ERROR_CONTACTING_SERVER, null, null); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV1 had an error when contacting server, remote threw an Exception."); e.printStackTrace(); Log.e(TAG, "Caused after network error:"); error.printStackTrace(); } sendError(listener, ERROR_CONTACTING_SERVER); }); } catch (AuthenticatorException | IOException | OperationCanceledException e) { sendError(listener, ERROR_CONTACTING_SERVER); } request.setShouldCache(false); queue.add(request); }, null); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "an app tried to request v1 licenses for package " + packageName + ", which does not exist"); Loading @@ -138,8 +161,18 @@ public class LicensingService extends Service { Log.e(TAG, "an app illegally tried to request v2 licenses for another app (caller: " + getCallingUid() + ")"); listener.verifyLicense(ERROR_NON_MATCHING_UID, new Bundle()); } else { Request request = new LicenseRequest.V2(packageName, versionCode, jwt -> { Account[] accounts = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE); if (accounts.length == 0) { Log.e(TAG, "not checking license, as user is not signed in"); } else accountManager.getAuthToken( accounts[0], AUTH_TOKEN_SCOPE, false, future -> { try { Bundle result = future.getResult(10, TimeUnit.SECONDS); String auth = result.getString(KEY_AUTHTOKEN); Request request = new LicenseRequest.V2(packageName, auth, versionCode, jwt -> { Log.v(TAG, "LicenseV2 returned JWT license value " + jwt); Bundle bundle = new Bundle(); bundle.putString(KEY_V2_RESULT_JWT, jwt); Loading @@ -151,18 +184,18 @@ public class LicensingService extends Service { } }, error -> { Log.e(TAG, "licenseV2 request failed with " + error.toString()); try { listener.verifyLicense(ERROR_CONTACTING_SERVER, new Bundle()); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV2 had an error when contacting server, remote threw an Exception."); e.printStackTrace(); Log.e(TAG, "Caused after network error:"); error.printStackTrace(); } sendError(listener, ERROR_CONTACTING_SERVER); }); request.setShouldCache(false); queue.add(request); } catch (AuthenticatorException | IOException | OperationCanceledException e) { sendError(listener, ERROR_CONTACTING_SERVER); e.printStackTrace(); } }, null ); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "an app tried to request v1 licenses for package " + packageName + ", which does not exist"); Loading @@ -172,8 +205,26 @@ public class LicensingService extends Service { } }; private static void sendError(ILicenseResultListener listener, int error) { try { listener.verifyLicense(error, null, null); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV1 had an error (" + error + "), remote threw an Exception."); } } private static void sendError(ILicenseV2ResultListener listener, int error) { try { listener.verifyLicense(error, new Bundle()); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV2 had an error (" + error + "), remote threw an Exception."); } } public IBinder onBind(Intent intent) { queue = Volley.newRequestQueue(this); accountManager = AccountManager.get(this); return mLicenseService; } Loading