Home | History | Annotate | Download | only in certinstaller
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.certinstaller;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.os.UserManager;
     24 import android.preference.PreferenceActivity;
     25 import android.provider.DocumentsContract;
     26 import android.security.Credentials;
     27 import android.security.KeyChain;
     28 import android.util.Log;
     29 import android.widget.Toast;
     30 
     31 import java.io.BufferedInputStream;
     32 import java.io.ByteArrayOutputStream;
     33 import java.io.IOException;
     34 import java.io.InputStream;
     35 import java.util.HashMap;
     36 import java.util.Map;
     37 
     38 import libcore.io.IoUtils;
     39 
     40 /**
     41  * The main class for installing certificates to the system keystore. It reacts
     42  * to the public {@link Credentials#INSTALL_ACTION} intent.
     43  */
     44 public class CertInstallerMain extends PreferenceActivity {
     45     private static final String TAG = "CertInstaller";
     46 
     47     private static final int REQUEST_INSTALL = 1;
     48     private static final int REQUEST_OPEN_DOCUMENT = 2;
     49 
     50     private static final String INSTALL_CERT_AS_USER_CLASS = ".InstallCertAsUser";
     51 
     52     public static final String WIFI_CONFIG = "wifi-config";
     53     public static final String WIFI_CONFIG_DATA = "wifi-config-data";
     54     public static final String WIFI_CONFIG_FILE = "wifi-config-file";
     55 
     56     private static Map<String,String> MIME_MAPPINGS = new HashMap<>();
     57 
     58     static {
     59             MIME_MAPPINGS.put("application/x-x509-ca-cert", KeyChain.EXTRA_CERTIFICATE);
     60             MIME_MAPPINGS.put("application/x-x509-user-cert", KeyChain.EXTRA_CERTIFICATE);
     61             MIME_MAPPINGS.put("application/x-x509-server-cert", KeyChain.EXTRA_CERTIFICATE);
     62             MIME_MAPPINGS.put("application/x-pem-file", KeyChain.EXTRA_CERTIFICATE);
     63             MIME_MAPPINGS.put("application/pkix-cert", KeyChain.EXTRA_CERTIFICATE);
     64             MIME_MAPPINGS.put("application/x-pkcs12", KeyChain.EXTRA_PKCS12);
     65             MIME_MAPPINGS.put("application/x-wifi-config", WIFI_CONFIG);
     66     }
     67 
     68     @Override
     69     protected void onCreate(Bundle savedInstanceState) {
     70         super.onCreate(savedInstanceState);
     71 
     72         setResult(RESULT_CANCELED);
     73 
     74         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
     75         if (userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
     76             finish();
     77             return;
     78         }
     79 
     80         final Intent intent = getIntent();
     81         final String action = intent.getAction();
     82 
     83         if (Credentials.INSTALL_ACTION.equals(action)
     84                 || Credentials.INSTALL_AS_USER_ACTION.equals(action)) {
     85             Bundle bundle = intent.getExtras();
     86 
     87             /*
     88              * There is a special INSTALL_AS_USER action that this activity is
     89              * aliased to, but you have to have a permission to call it. If the
     90              * caller got here any other way, remove the extra that we allow in
     91              * that INSTALL_AS_USER path.
     92              */
     93             String calledClass = intent.getComponent().getClassName();
     94             String installAsUserClassName = getPackageName() + INSTALL_CERT_AS_USER_CLASS;
     95             if (bundle != null && !installAsUserClassName.equals(calledClass)) {
     96                 bundle.remove(Credentials.EXTRA_INSTALL_AS_UID);
     97             }
     98 
     99             // If bundle is empty of any actual credentials, ask user to open.
    100             // Otherwise, pass extras to CertInstaller to install those credentials.
    101             // Either way, we use KeyChain.EXTRA_NAME as the default name if available.
    102             if (bundle == null
    103                     || bundle.isEmpty()
    104                     || (bundle.size() == 1
    105                         && (bundle.containsKey(KeyChain.EXTRA_NAME)
    106                             || bundle.containsKey(Credentials.EXTRA_INSTALL_AS_UID)))) {
    107                 final String[] mimeTypes = MIME_MAPPINGS.keySet().toArray(new String[0]);
    108                 final Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    109                 openIntent.setType("*/*");
    110                 openIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
    111                 openIntent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
    112                 startActivityForResult(openIntent, REQUEST_OPEN_DOCUMENT);
    113             } else {
    114                 final Intent installIntent = new Intent(this, CertInstaller.class);
    115                 installIntent.putExtras(intent);
    116                 startActivityForResult(installIntent, REQUEST_INSTALL);
    117             }
    118         } else if (Intent.ACTION_VIEW.equals(action)) {
    119             startInstallActivity(intent.getType(), intent.getData());
    120         }
    121     }
    122 
    123     // The maximum amount of data to read into memory before aborting.
    124     // Without a limit, a sufficiently-large file will run us out of memory.  A
    125     // typical certificate or WiFi config is under 10k, so 10MiB should be more
    126     // than sufficient.  See b/32320490.
    127     private static final int READ_LIMIT = 10 * 1024 * 1024;
    128 
    129     /**
    130      * Reads the given InputStream until EOF or more than READ_LIMIT bytes have
    131      * been read, whichever happens first.  If the maximum limit is reached, throws
    132      * IOException.
    133      */
    134     private static byte[] readWithLimit(InputStream in) throws IOException {
    135         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    136         byte[] buffer = new byte[1024];
    137         int bytesRead = 0;
    138         int count;
    139         while ((count = in.read(buffer)) != -1) {
    140             bytes.write(buffer, 0, count);
    141             bytesRead += count;
    142             if (bytesRead > READ_LIMIT) {
    143                 throw new IOException("Data file exceeded maximum size.");
    144             }
    145         }
    146         return bytes.toByteArray();
    147     }
    148 
    149     private void startInstallActivity(String mimeType, Uri uri) {
    150         if (mimeType == null) {
    151             mimeType = getContentResolver().getType(uri);
    152         }
    153 
    154         String target = MIME_MAPPINGS.get(mimeType);
    155         if (target == null) {
    156             throw new IllegalArgumentException("Unknown MIME type: " + mimeType);
    157         }
    158 
    159         if (WIFI_CONFIG.equals(target)) {
    160             startWifiInstallActivity(mimeType, uri);
    161         }
    162         else {
    163             InputStream in = null;
    164             try {
    165                 in = getContentResolver().openInputStream(uri);
    166 
    167                 final byte[] raw = readWithLimit(in);
    168                 startInstallActivity(target, raw);
    169 
    170             } catch (IOException e) {
    171                 Log.e(TAG, "Failed to read certificate: " + e);
    172                 Toast.makeText(this, R.string.cert_read_error, Toast.LENGTH_LONG).show();
    173             } finally {
    174                 IoUtils.closeQuietly(in);
    175             }
    176         }
    177     }
    178 
    179     private void startInstallActivity(String target, byte[] value) {
    180         Intent intent = new Intent(this, CertInstaller.class);
    181         intent.putExtra(target, value);
    182 
    183         startActivityForResult(intent, REQUEST_INSTALL);
    184     }
    185 
    186     private void startWifiInstallActivity(String mimeType, Uri uri) {
    187         Intent intent = new Intent(this, WiFiInstaller.class);
    188         try (BufferedInputStream in =
    189                      new BufferedInputStream(getContentResolver().openInputStream(uri))) {
    190             byte[] data = readWithLimit(in);
    191             intent.putExtra(WIFI_CONFIG_FILE, uri.toString());
    192             intent.putExtra(WIFI_CONFIG_DATA, data);
    193             intent.putExtra(WIFI_CONFIG, mimeType);
    194             startActivityForResult(intent, REQUEST_INSTALL);
    195         } catch (IOException e) {
    196             Log.e(TAG, "Failed to read wifi config: " + e);
    197             Toast.makeText(this, R.string.cert_read_error, Toast.LENGTH_LONG).show();
    198         }
    199     }
    200 
    201     @Override
    202     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    203         if (requestCode == REQUEST_OPEN_DOCUMENT) {
    204             if (resultCode == RESULT_OK) {
    205                 startInstallActivity(null, data.getData());
    206             } else {
    207                 finish();
    208             }
    209         } else if (requestCode == REQUEST_INSTALL) {
    210             setResult(resultCode);
    211             finish();
    212         } else {
    213             Log.w(TAG, "unknown request code: " + requestCode);
    214         }
    215     }
    216 }
    217