30

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) {
OneWorld
  • 17,512
  • 21
  • 86
  • 136
Pedro
  • 443
  • 1
  • 4
  • 7
  • There's no receiver listening for that `Intent` - just an activity in the system, and for good reasons - allowing any malicious random app to install root CA's silently would be a omghuge security hole. – Jens Jul 25 '12 at 19:55

7 Answers7

19

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.

Nikolay Elenkov
  • 52,576
  • 10
  • 84
  • 84
  • It also forces you to protect your device either with a numeric PIN code or with an unlock pattern for the lockscreen, which is kind of annoying but understandable in terms of user security. I guess they want to be sure that the one installing the certificate will also be the owner of the device. – Sebastiano Feb 12 '13 at 15:49
  • 1
    Something like that, however if there is no password/PIN to begin with, you can set it to whatever you want. The password/PIN is then used to derive a master key to encrypt private keys with. Certificates get encrypted with it too, which is not strictly necessary. – Nikolay Elenkov Feb 12 '13 at 17:09
  • 4
    `installCaCert` has become visible in [SDK 21](http://developer.android.com/reference/android/app/admin/DevicePolicyManager.html) and apparently available to device administrators. – Rupert Rawnsley Oct 19 '15 at 09:33
  • @RupertRawnsley AFAIK, it only works if your device admin is also the device owner ('super admin') – Nikolay Elenkov Oct 21 '15 at 03:20
  • Device owner is a new one on me. System access via NFC - what could possibly go wrong? ;-) – Rupert Rawnsley Oct 21 '15 at 08:02
9

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.

OneWorld
  • 17,512
  • 21
  • 86
  • 136
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • And, by any chance, do someone know how to detect the Certificate installation intent ? (after the user input on the cert password & name ? ) I have found no freaking way to check that my CA security cert is correctly installed. Even before the user have typed the password to extract the certificate, when I request the KeyStore, the certificate is present so I can't know wheter or not the user have completed the installation. – Alex Nov 03 '16 at 17:29
8

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 :)

Chris Noldus
  • 2,432
  • 2
  • 20
  • 27
  • This is not possible until our application shares the UID. `final String actualPackage = getPackageManager().getNameForUid(getCallingUid()); if (!expectedPackage.equals(actualPackage)) { throw new IllegalStateException(actualPackage); }` validates the caller. Can you explain how did you managed without sharing UID? – Pankaj Kumar May 07 '14 at 10:03
  • @Pankaj - We use the sysytem uid in the sharedUserId property of the Android Manifest for our system apps, perhaps this is why I did not need to do it programatically as you have. – Chris Noldus Jan 24 '15 at 10:58
  • `E AndroidRuntime: java.lang.NoSuchMethodError: No interface method installCaCertificate([B)Ljava/lang/String; in class Landroid/se curity/IKeyChainService; or its super classes (declaration of 'android.security.IKeyChainService' appears in /system/framework/framework.jar)` – Yeung Jun 01 '20 at 06:18
  • 1
    By looking into `DevicePolicyManager`, the `IKeyChainService` is obtained by `KeychainConnection keychainConnection = KeyChain.bind(context); keychainConnection.getService()`. For System App developers and who do not want to mess with `DevicePolicyManager`, we can obtain `IKeyChainService` by reflection. Anyway, I tried and installed a third party cacert successfully, but the security setting screen have shown a "triangle" to warn the user that a third party cacert somehow is installed. Seem Android System know it is not come from "standard way", although it does not affect the use case.. – Yeung Jun 03 '20 at 10:27
  • I can't get the first step working. I"ve imported the IKeyChainService.aidl in /src/main/aidl/android.security. I have to comment line nr 30 `UnsupportedAppUsage`. But then the project won't build either because of `couldn't find import for class android.content.pm.StringParceledListSlice`. When I create those missing classes manually with `parcelable StringParceledListSlice;` etc. Then compilation still fails with: `error: package android.security.keymaster does not exist public int attestKey(..android.security.keymaster.KeymasterCertificateChain c..` All help is welcome! – Kapitein Witbaard Jun 17 '20 at 15:24
4

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!

  • This gives me an error, i.e, "java.lang.NoSuchMethodException: android.security.KeyChain.bind [class android.content.Context]" Any idea how I can go through this? – vidulaJ Mar 26 '21 at 09:53
  • Which Android Version are you on, @vidulaJ? The KeyChain class in any recent Android Version is definitely supposed to contain the "bind" method, see official Sources: https://android.googlesource.com/platform/frameworks/base.git/+/master/keystore/java/android/security/KeyChain.java At the time of writing this, the "bind" method is defined in line 879 and the source file. Even if we go back to Jelly Bean (Android 4.1), the method exists, see line 410 here: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/jb-release/keystore/java/android/security/KeyChain.java – Michael Stiefler Mar 29 '21 at 07:55
  • I run this code in Android 10 and even when I try the methods directly declaring an object from KeyChain or KeyChainConnection. But even when doing so, public static KeyChainConnection bind(@NonNull Context context) throws InterruptedException, cannot be accessed. Cannot think what is wrong with my code. – vidulaJ Mar 29 '21 at 10:57
  • Also, I cannot find 'bind' method in the official documentation. https://developer.android.com/reference/kotlin/android/security/KeyChain – vidulaJ Mar 29 '21 at 11:20
  • Of course "bind" isn't in the official documentation, it's intentionally tagged with the "@hide" flag to keep developers from accessing this method. Such methods are intended to be used internally by Android OS only, but as you can see from the code example I've posted on July 22 2020, you can use the Reflection API to gain access to the methods, _provided your app has privileged system level access, i.e. it needs to be signed with the target device's platform key_, otherwise you cannot declare the required android:sharedUserId in your app's manifest. – Michael Stiefler Mar 30 '21 at 11:08
2

If you have root privilege, you could copy the certs file to /data/misc/user/0/cacerts-added/

ospider
  • 9,334
  • 3
  • 46
  • 46
  • Hi. I tried to do this, at first it seemed to work, but when an application i need to configure tried to connect to its server, it failed because of a certificate problem. There is another thing to do beside copying the cert to that folder? – Sebastián Rodriguez Jan 31 '18 at 13:26
  • @SebastiánRodriguez which version were you using? – ospider Feb 01 '18 at 08:57
  • Version of what? After my question, i managed to succesfully install the cert copying it to `/data/misc/user/0/cacerts-added/` with a name that aparently is unique and is managed by Android internally. I also had to chmod and chown the copied cert. – Sebastián Rodriguez Feb 02 '18 at 14:42
2

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.

1

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.

Konjengbam
  • 99
  • 3