I'm trying to install certificates without prompting the user. I know this is not good practice, but that's what PM wants.
Using KeyChain.createInstallIntent(), I can get Android to launch the certificate installation dialog by calling startActivity. However, when I pass the intent to sendBroadcast, nothing happens. Maybe the platform doesn't support this for security reasons?
String CERT_FILE = Environment.getExternalStorageDirectory() + "/test/IAT.crt";
Intent intent = KeyChain.createInstallIntent();
try {
FileInputStream certIs = new FileInputStream(CERT_FILE);
byte [] cert = new byte[(int)certFile.length()];
certIs.read(cert);
X509Certificate x509 = X509Certificate.getInstance(cert);
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, x509.getEncoded());
intent.putExtra(KeyChain.EXTRA_NAME, "IAT Cert");
EapActivity.this.startActivityForResult(intent, 0); // this works but shows UI
EapActivity.this.sendBroadcast(intent); // this doesn't install cert
} catch (IOException e) {
You can only install certificates silently if you have system privileges. Showing up a confirmation dialog is intentional, since trusting certificates can have serious consequences -- Android could happily open phishing sites without a warning, etc. That said, the dialog in ICS/JB is pretty bad -- it doesn't tell you what certificate you are installing and who issued it, just that it's a CA certificate, which is kind of obvious.
So, either use the public KeyChain API and use startActivity() to get the confirmation dialog, or pre-provision devices before handling them to users.
Update: In Android 4.4, DevicePolicyManager has a hidden API (installCaCert) that allows you to install certificates silently. You need the MANAGE_CA_CERTIFICATES permission, which is signature|system, so still not doable for user-installed apps.
Using KeyChain.createInstallIntent(), I can get Android to launch the certificate installation dialog by calling startActivity. However, when I pass the intent to sendBroadcast, nothing happens.
Few if any Intent objects that you would pass to startActivity() would work with sendBroadcast(). They are independent channels of the quasi-message bus that is the Intent system.
For non-system app developers - the simple answer is it can not be done without user interaction.
For System App developers, I found the following solution, NB you must run the app with the system user id and sign the app with the system key or the service will reject your attempts to install the certificate.
Step 1 - Create interface
Create a new package in your project: android.security, then copy IKeyChainService.aidl into this package.
Step 2 - Bind to service and install certificate
The Activity gives an example of how to install a CA certificate:
public class KeyChainTest extends Activity {
private final Object mServiceLock = new Object();
private IKeyChainService mService;
private boolean mIsBoundService =false;
private ServiceConnection mServiceConnection = new ServiceConnection() {
#Override public void onServiceConnected(ComponentName name,
IBinder service) {
synchronized (mServiceLock) {
mService = IKeyChainService.Stub.asInterface(service);
mServiceLock.notifyAll();
try {
byte[] result = YOUR_CA_CERT_AS_BYTE_ARRAY
//The next line actually installs the certificate
mService.installCaCertificate(result);
} catch (Exception e) {
//EXception handling goes here
}
}
}
#Override public void onServiceDisconnected(ComponentName name) {
synchronized (mServiceLock) {
mService = null;
}
}
};
private void bindService() {
mIsBoundService = bindService(new Intent(IKeyChainService.class.getName()),
mServiceConnection,
Context.BIND_AUTO_CREATE);
}
private void unbindServices() {
if (mIsBoundService) {
unbindService(mServiceConnection);
mIsBoundService = false;
}
}
#Override public void onDestroy () {
unbindServices();
}
#Override
protected void onStart() {
super.onStart();
// Bind to KeyChainService
bindService();
}
}
I hope this helps someone - it took me a long time to work it out :)
This thread is a bit dated already, nevertheless since I stumbled upon the same issue and couldn't find any "out of the box" solution for Android O or later, I thought I'd share what I came up with and which worked well for me when trying to install Certificates (CA and others) to the Android Trusted credentials "User" store:
// Supply context, e.g. from "Context context = getApplicationContext();"
// String fileName points to the file holding the certificate to be installed. pem/der/pfx tested.
RandomAccessFile file = new RandomAccessFile(fileName, "r");
byte[] certificateBytes = new byte[(int)file.length()];
file.read(certificateBytes);
Class<?> keyChainConnectionClass = Objects.requireNonNull(context.getClassLoader()).loadClass("android.security.KeyChain$KeyChainConnection");
Class<?> iKeyChainServiceClass = Objects.requireNonNull(context.getClassLoader()).loadClass("android.security.IKeyChainService");
Method keyChainBindMethod = KeyChain.class.getMethod("bind", Context.class);
Method keyChainConnectionGetServiceMethod = keyChainConnectionClass.getMethod("getService");
Object keyChainConnectionObject = keyChainBindMethod.invoke(null, context);
Object iKeyChainServiceObject = keyChainConnectionGetServiceMethod.invoke(keyChainConnectionObject);
Method installCaCertificate = iKeyChainServiceClass.getDeclaredMethod("installCaCertificate", byte[].class);
installCaCertificate.invoke(iKeyChainServiceObject, certificateBytes);
Note that if you want to silently install a certificate this way, your app needs to be a system app, i.e. it needs to have
android:sharedUserId="android.uid.system"
declared in it's manifest.
Cheers!
If you have root privilege, you could copy the certs file to /data/misc/user/0/cacerts-added/
Based on the #ospider's answer, i managed to succesfully install the cert like this way:
adb shell mkdir -p /data/misc/user/0/cacerts-added
adb push certificate.cer /data/misc/user/0/cacerts-added/807e3b02.0
# Maybe these two lines are not strictly necessary...
adb shell chmod 644 /data/misc/user/0/cacerts-added/807e3b02.0
adb shell chown system:system /data/misc/user/0/cacerts-added/807e3b02.0
I got the name of the copied file (807e3b02.0) by installing manually the cert i wanted to automate and seeing how Android saved it (whith adb shell ls -l /data/misc/user/0/cacerts-added/)
Hope this help.
Regards.
Only a system user application can silently install a CA certificate. On Lollipop though, Google introduced silent certificate management API through DevicePolicyManager, but you would either have to be Android-for-Work profile owner or device owner.
Related
I need to check if a clone of my app is installed on an Android device.
It is a big security concern for my ongoing project.
Update:
I've now realised that clones are not installed but are actually able to run as a seperate instance without disturbing the original one.
Example of an app cloner:
https://play.google.com/store/apps/details?id=com.lbe.parallel.intl
How am I supposed to check at runtime that the running app has only one instance?
Android utilizes unique package strings to identify applications. Your application has a unique package string associated with it just like all Android apps.
To check if an app is installed on the device, you use the Package Manager and check with this code:
public static boolean doesUserHaveFacebookAppInstalled(Context context,
String packageString){
try{
context.getPackageManager().getApplicationInfo(packageString, 0 );
return true;
} catch( PackageManager.NameNotFoundException e ){
return false;
}
}
So if you wanted to check if, for example, Facebook is installed, you would pass in their package string of com.facebook.katana. If it returns true, it is installed, if it is false, it is not installed.
In your situation, only one package is allowed on a device at a time, but the package String can be altered when creating a build. IE, your app package String could be com.myapp.somename and a debug version could be com.myapp.somename-debug. Both would be allowed to be installed as they are unique package Strings.
If you want to check what apps are installed on the device, you can use this code and it will get all installed applications that can be launched / opened:
public static List<ResolveInfo> getAllInstalledApps(Context context){
try {
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> pkgAppsList = context.getPackageManager()
.queryIntentActivities( mainIntent, 0);
for(ResolveInfo r : pkgAppsList){
if(r != null) {
//You can use this String here to identify it while looping.
String packageString = r.toString();
}
}
return pkgAppsList;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Use the results of the second code snippet to identify what apps are installed and check against the package string variations for your app to determine if other ones are installed.
There is no generic solution for this problem; some dual apps hook the system API, some run the app with different userId, and some clone the apk to a single mini VM to run.
The best approach, in my opinion, is to make your app can log-in at one place only when you open the app, connect to your server and your server forces to offline the previously logged-in device.
In that case, you may need a long connection or publish-subscribe-based messaging framework in your project (e.g., MQTT)
My android application will be preinstalled. And I want to keep tracking of preinstalled apps.
For this purpose I need somehow to save a key or a flag (which means that app is preinstalled). I will add this key to each request to my back-end and will analyze it.
I have an issue with that. An issue is about update from Google Play.
The standart workflow is the following:
1) I give to a manufacturer a special version of my application, which saves a key somehow (in Shared Prefs for example).
2) Manufacturer sell device with the app (special, modified).
3) When User get it, there definetly be next version of the app (standart, without special code) in the Google Play, so user perhaps update it without any launching (the worst case).
4) I lost my tracking possibility. (new apk fully removing never launched old one which was special)
To solve it I was listening a system broadcast ON_BOOT_COMPLETE, but its not working properly on Android 3.1+.
Have you any ideas how can I do that?
Can you install an additional .apk that only has a service? Then that service can have the key, etc. and it can listen for when your app starts and send the tracking info. Then it won't matter if your app gets upgraded; the service will still be the same.
There are some ways to know if application is system application or not. Like by checking installed directory of application or check FLAG_SYSTEM for the application.
Method 1 : -
Check location of application
public static boolean applicationIsSystemApp(Context mContext, String packageName) {
try {
ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
String appLocation = applicationInfo.publicSourceDir;
// OR String appLocation = applicationInfo.sourceDir;
// Both returns the same
// if package is pre-installed then output will be /system/app/application_name.apk
// if package is installed by user then output will be /data/app/application_name.apk
// Check if package is system app
if (appLocation != null && appLocation.startsWith("/system/app/")) {
return true;
}
} catch (NameNotFoundException e) {
e.printStackTrace(); // TODO Can handle as your logic
}
return false;
}
Method 2 : -
Check FLAG_SYSTEM of application
public static boolean applicationIsSystemApp(Context mContext, String packageName) {
try {
ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
// FLAG_SYSTEM is only set to system applications,
// this will work even if application is installed in external storage
// Check if package is system app
if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
return true;
}
} catch (NameNotFoundException e) {
e.printStackTrace(); // TODO Can handle as your logic
}
return false;
}
And call this method as
if (applicationIsSystemApp(getApplicationContext(), "com.example.mysystemapp")) {
// Application is system app
} else {
// Application has been installed as 3rd Party app
}
There is a property sourceDir in ApplicationInfo class. You can use it to distinguish the system app version and the upgraded one.
System app will start with "/system/app" and upgraded app will start with "/data/app"
Try something like this
try {
ApplicationInfo appInfo = this.getPackageManager().getApplicationInfo("com.example.san", 0);
boolean isSystemApp = false;
if(appInfo.sourceDir.startsWith("/system/app")){ // You can use "contains" too
isSystemApp = true;
}
} catch (Exception e) {
e.printStackTrace();
}
Note: I didnt test it.. Hope it works
One proven solution is to pre-install an APK that on have the permission: RECEIVE_BOOT_COMPLETED
Then on the very first boot - you make a quick notation that you are a PREINSTALLED to whatever persistent storage you use, preferably add a token file.
You look for this token file on all your later APK versions to determine if the running copy originates from a device which had it pre-installed or not.
This solves the mayor issues:
1) Then its OK if the user updates you APK to the latest version, you can still read this token.
2) You don't have to maintain a separate APK on google play for the pre-installed community
3) You don't have to hustle with the OEM to install multiple APK when you actually only have one App.
I develop an app and want update itself and want following fetures, device have been rooted :
1 automatic check can update every start (I can do)
2 download the apk file to local (I can do)
3 update with custom dialog, or update silently (I dont know )
edit:
My app run on TV with remote, the default dialog which can control but perfect , so I want use my dialog if there must a dialog.Its best if need not a dialog.
First declare this variables, then call function wherever you want. Then grant superuser, on your superuser application, check the option to always grant, for non user interaction.
#Override
public void onCreate() {
super.onCreate();
//some code...
final String libs = "LD_LIBRARY_PATH=/vendor/lib:/system/lib ";
final String commands = libs + "pm install -r " + "your apk directory"+ "app.apk";
instalarApk(commands);
}
private void instalarApk( String commands ) {
try {
Process p = Runtime.getRuntime().exec( "su" );
InputStream es = p.getErrorStream();
DataOutputStream os = new DataOutputStream(p.getOutputStream());
os.writeBytes(commands + "\n");
os.writeBytes("exit\n");
os.flush();
int read;
byte[] buffer = new byte[4096];
String output = new String();
while ((read = es.read(buffer)) > 0) {
output += new String(buffer, 0, read);
}
p.waitFor();
} catch (IOException e) {
Log.v(Debug.TAG, e.toString());
} catch (InterruptedException e) {
Log.v(Debug.TAG, e.toString());
}
}
The regular way (without root and a default installer dialog) is described in another question, and should be used in most cases imho, as I dislike requesting root privileges for a update feature.
There is no difference in providing a custom dialog and installing an apk silently, as both technically are silent installs (e.g. not made by the install activity). With root privileges, you could use a root shell and either replace the apk (as in the open-source keybord manager app) or invoke the package manager from shell. I'd suggest you go for the second way, I linked the keybord manager app source mainly for the root shell creation stuff.
The Android Framework make it defficult to update silently, on custom devices you must call system install service for your apk file, and it will show the install dialog for the user, but if the device is rooted, I think you can make that silently.
This question already has answers here:
Way to protect from Lucky Patcher / play licensing [closed]
(8 answers)
Closed 5 years ago.
I know this topic has been opened multiple times and I learnt a lot but I stumbled across a problem I really need advice on.
I'm using LVL with Obfuscation. I changed the default LVL ALOT so that anti-LVL does not break it. However, Lucky Patcher with one click breaks it! I tried to see the new broken APK. Yes it simply called my "allow method".
My question is if someone can recommend a way to prevent Lucky Patcher from breaking it? I know I can't make it bullet-proof, but I want it at least to be not so easy for one-click software.
Code to check your certificate:
public void checkSignature(final Context context) {
try {
Signature[] signatures = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
if (signatures[0].toCharsString() != <YOUR CERTIFICATE STRING GOES HERE>) {
// Kill the process without warning. If someone changed the certificate
// is better not to give a hint about why the app stopped working
android.os.Process.killProcess(android.os.Process.myPid());
}
}
catch (NameNotFoundException ex) {
// Must never fail, so if it does, means someone played with the apk, so kill the process
android.os.Process.killProcess(android.os.Process.myPid());
}
}
Next how to find which one is your certificate. You must produce an APK, in release mode, as the debug certificate is different from the release one. Output your certificate into your Logcat:
signatures[0].toCharsString();
Remember that when you are back to debug mode, the certificate is different again. To avoid debug issues use next line to skip the verification:
if ((context.getApplicationContext().getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE) != 0)
return;
Next the lucky patcher checker.
I decompiled all versions of Lucky Patcher, and I've found that its creator used 2 package names between all realeases. So you only need to keep track of new versions and keep adding future package names.
private boolean checkLuckyPatcher() {
if (packageExists("com.dimonvideo.luckypatcher"))
return true;
if (packageExists("com.chelpus.lackypatch"))
return true;
if (packageExists("com.android.vending.billing.InAppBillingService.LACK"))
return true;
return false;
}
private boolean packageExists(final String packageName) {
try {
ApplicationInfo info = this.getPackageManager().getApplicationInfo(packageName, 0);
if (info == null) {
// No need really to test for null, if the package does not
// exist it will really rise an exception. but in case Google
// changes the API in the future lets be safe and test it
return false;
}
return true;
}
catch (Exception ex) {
// If we get here only means the Package does not exist
}
return false;
}
As of current version (6.4.6), Lucky Patcher generates very short token. For example, real purchase token:
felihnbdiljiajicjhdpcgbb.AO-J1OyQgD6gEBTUHhduDpATg3hLkTYSWyVZUvFwe4KzT3r-O7o5kdt_PbG7sSUuoC1l6dtqsYZW0ZuoEkVUOq5TMi8LO1MvDwdx5Kr7vIHCVBDcjCl3CKP4UigtKmXotCUd6znJ0KfW
And that is Lucky Token:
kvfmqjhewuojbsfiwqngqqmc
Pretty straight forward solution is to check string length of token
#Override public void onIabPurchaseFinished(IabResult result, Purchase info) {
if (info.getToken().length < 25) {
Log.wtf("PIRATE", "PIRATE DETECTED");
return;
}
}
Implement a function that gets called under certain actions, and which checks whether the LuckyPatcher package is installed in the device.
If found, then exit your app. Don’t allow to use it regardless if is paid or not, better bad reviews than thousands of illegal copies. Alternatively you could show a message stating that LuckyPatcher has been found and the app can't run.
If your app gets patched by LuckyPatcher, meaning that it has hacked your LVL implementation, then at least your app will not execute due to the LuckyPatcher package detection.
A way, is to check if lucky patcher is installed and if so, then show a message to the user, and kill your process afterwards. If a user has it, means he is trying to crack your software or other developer's one. So better not to allow to use your app in a phone that has it installed. Fight piracy.
Whenever Lucky Patcher creates a modded APK file, it always ends up with a different package name, as you can't run two apps under the same package name.
Here's a simple solution that checks if your code is running under the wrong package name:
PackageManager pm = getPackageManager();
try {
PackageInfo packageInfo = pm.getPackageInfo("YOUR_PACKAGE_NAME",PackageManager.GET_ACTIVITIES);
} catch (PackageManager.NameNotFoundException e){
finish();
//If you get here, your code is running under a different package name... Kill the process!
}
I just call finish(); on my app and I can't break it, but it might be best to use android.os.Process.killProcess(android.os.Process.myPid()); as #PerracoLabs suggested.
I am stuck writing some code that uses reflection that calls IConnectivityManager.startLegacyVpn
The error I get is java.lang.SecurityException: Unauthorized Caller
Looking through the android source I see this is the code hanging me up:
if (Binder.getCallingUid() != Process.SYSTEM_UID) { raise the above exception }
My question is if I root my AVD and install my app in system/app will this be sufficient to get around this error?
If so, any tips on how to do this (every time I try to move my apk to the system/app folder it says the app is not installed when I click on the app icon.
Thanks!
I have the same problem, following android 4.01 open source, i see somethings like this:
public synchronized LegacyVpnInfo getLegacyVpnInfo() {
// Only system user can call this method.
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Unauthorized Caller");
}
return (mLegacyVpnRunner == null) ? null : mLegacyVpnRunner.getInfo();
}
Or,
// Only system user can revoke a package.
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Unauthorized Caller");
}
Or,
public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
PackageManager pm = mContext.getPackageManager();
ApplicationInfo app = pm.getApplicationInfo(mPackage, 0);
if (Binder.getCallingUid() != app.uid) {
throw new SecurityException("Unauthorized Caller");
}
jniProtect(socket.getFd(), interfaze);
}
However, these block of code above is belongs to com.android.server.connectivity.Vpn
(class Vpn), which is not defined in interface IConnectivityManager.
I also find in startLegacyVpnInfo() function but i can't see anything involve exception
"Unauthorized Caller", so i wonder why startLegacyVpnInfo() function throws this exception?
Any solutions for this?
I am trying to make the same calls. So far I can confirm that rooting the device and copying the apk to /system/app does not work, it does not start under the system uid.
Also, this does not work:
Field uidField = Process.class.getDeclaredField("SYSTEM_UID");
uidField.setAccessible(true);
uidField.set(null, Process.myUid());
Those calls succeed, but they don't seem to affect the SYSTEM_UID field, the field is probably optimized out at compile time.
If you include android: sharedUserId="android.uid.system" into your manifest tag (not just the manifest), this should then run the application as system. This should now let you run the code.
As for pushing to /system/app, you need to run adb root followed by adb remount. This will now let you push to /system/app.