• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate(Bundle savedInstanceState)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      */
readWithLimit(InputStream in)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 
startInstallActivity(String mimeType, Uri uri)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 
startInstallActivity(String target, byte[] value)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 
startWifiInstallActivity(String mimeType, Uri uri)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
onActivityResult(int requestCode, int resultCode, Intent data)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