blob: 7e9388491b2f703ce5c2b802fc1bc6e3aedce10e [file] [log] [blame]
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +08001/*
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
17package com.android.certinstaller;
18
Julia Reynoldsb7fe4752014-06-12 13:40:57 -040019import android.content.Context;
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +080020import android.content.Intent;
Ben Komaloc1615f62011-07-21 15:04:27 -070021import android.net.Uri;
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +080022import android.os.Bundle;
Julia Reynoldsb7fe4752014-06-12 13:40:57 -040023import android.os.UserManager;
Jeff Sharkey88ded902013-10-25 14:53:06 -070024import android.preference.PreferenceActivity;
25import android.provider.DocumentsContract;
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +080026import android.security.Credentials;
Ben Komaloc1615f62011-07-21 15:04:27 -070027import android.security.KeyChain;
28import android.util.Log;
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +080029import android.widget.Toast;
30
Jan Nordqvist994932f2015-03-25 18:56:30 -070031import java.io.BufferedInputStream;
Adam Vartanianc8a7fa42018-02-23 14:34:25 +000032import java.io.ByteArrayOutputStream;
Ben Komaloc1615f62011-07-21 15:04:27 -070033import java.io.IOException;
34import java.io.InputStream;
Jan Nordqvist994932f2015-03-25 18:56:30 -070035import java.util.HashMap;
36import java.util.Map;
37
38import libcore.io.IoUtils;
Ben Komaloc1615f62011-07-21 15:04:27 -070039
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +080040/**
41 * The main class for installing certificates to the system keystore. It reacts
42 * to the public {@link Credentials#INSTALL_ACTION} intent.
43 */
Jeff Sharkey88ded902013-10-25 14:53:06 -070044public 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
Kenny Root38a95642014-05-08 13:28:39 -070050 private static final String INSTALL_CERT_AS_USER_CLASS = ".InstallCertAsUser";
51
Jan Nordqvist994932f2015-03-25 18:56:30 -070052 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 }
Jeff Sharkey88ded902013-10-25 14:53:06 -070067
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +080068 @Override
69 protected void onCreate(Bundle savedInstanceState) {
70 super.onCreate(savedInstanceState);
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +080071
Jeff Sharkey88ded902013-10-25 14:53:06 -070072 setResult(RESULT_CANCELED);
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +080073
Julia Reynoldsb7fe4752014-06-12 13:40:57 -040074 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
75 if (userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
76 finish();
77 return;
78 }
79
Jeff Sharkey88ded902013-10-25 14:53:06 -070080 final Intent intent = getIntent();
81 final String action = intent.getAction();
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +080082
Kenny Root53622fc2013-03-28 11:08:18 -070083 if (Credentials.INSTALL_ACTION.equals(action)
84 || Credentials.INSTALL_AS_USER_ACTION.equals(action)) {
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +080085 Bundle bundle = intent.getExtras();
Kenny Root53622fc2013-03-28 11:08:18 -070086
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 */
Kenny Root38a95642014-05-08 13:28:39 -070093 String calledClass = intent.getComponent().getClassName();
94 String installAsUserClassName = getPackageName() + INSTALL_CERT_AS_USER_CLASS;
95 if (bundle != null && !installAsUserClassName.equals(calledClass)) {
Kenny Root53622fc2013-03-28 11:08:18 -070096 bundle.remove(Credentials.EXTRA_INSTALL_AS_UID);
97 }
98
Jeff Sharkey88ded902013-10-25 14:53:06 -070099 // If bundle is empty of any actual credentials, ask user to open.
Brian Carlstromf4616bf2012-03-30 16:07:58 -0700100 // 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()
Kenny Root53622fc2013-03-28 11:08:18 -0700104 || (bundle.size() == 1
105 && (bundle.containsKey(KeyChain.EXTRA_NAME)
106 || bundle.containsKey(Credentials.EXTRA_INSTALL_AS_UID)))) {
Jeff Sharkey655de812015-04-18 13:14:24 -0700107 final String[] mimeTypes = MIME_MAPPINGS.keySet().toArray(new String[0]);
Jeff Sharkey88ded902013-10-25 14:53:06 -0700108 final Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
109 openIntent.setType("*/*");
Jeff Sharkey655de812015-04-18 13:14:24 -0700110 openIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
Aga Wronska55e50202016-03-21 15:20:35 -0700111 openIntent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
Jeff Sharkey88ded902013-10-25 14:53:06 -0700112 startActivityForResult(openIntent, REQUEST_OPEN_DOCUMENT);
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +0800113 } else {
Jeff Sharkey88ded902013-10-25 14:53:06 -0700114 final Intent installIntent = new Intent(this, CertInstaller.class);
115 installIntent.putExtras(intent);
116 startActivityForResult(installIntent, REQUEST_INSTALL);
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +0800117 }
Ben Komaloc1615f62011-07-21 15:04:27 -0700118 } else if (Intent.ACTION_VIEW.equals(action)) {
Jeff Sharkey88ded902013-10-25 14:53:06 -0700119 startInstallActivity(intent.getType(), intent.getData());
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +0800120 }
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +0800121 }
122
Adam Vartanianc8a7fa42018-02-23 14:34:25 +0000123 // 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
Jeff Sharkey88ded902013-10-25 14:53:06 -0700149 private void startInstallActivity(String mimeType, Uri uri) {
150 if (mimeType == null) {
151 mimeType = getContentResolver().getType(uri);
152 }
153
Jan Nordqvist994932f2015-03-25 18:56:30 -0700154 String target = MIME_MAPPINGS.get(mimeType);
155 if (target == null) {
Jeff Sharkey88ded902013-10-25 14:53:06 -0700156 throw new IllegalArgumentException("Unknown MIME type: " + mimeType);
Ben Komaloc1615f62011-07-21 15:04:27 -0700157 }
Jeff Sharkey88ded902013-10-25 14:53:06 -0700158
Jan Nordqvist994932f2015-03-25 18:56:30 -0700159 if (WIFI_CONFIG.equals(target)) {
160 startWifiInstallActivity(mimeType, uri);
161 }
162 else {
163 InputStream in = null;
164 try {
165 in = getContentResolver().openInputStream(uri);
166
Adam Vartanianc8a7fa42018-02-23 14:34:25 +0000167 final byte[] raw = readWithLimit(in);
Jan Nordqvist994932f2015-03-25 18:56:30 -0700168 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
Jeff Sharkey88ded902013-10-25 14:53:06 -0700183 startActivityForResult(intent, REQUEST_INSTALL);
Ben Komaloc1615f62011-07-21 15:04:27 -0700184 }
185
Jan Nordqvist994932f2015-03-25 18:56:30 -0700186 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))) {
Adam Vartanianc8a7fa42018-02-23 14:34:25 +0000190 byte[] data = readWithLimit(in);
Jan Nordqvist994932f2015-03-25 18:56:30 -0700191 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
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +0800201 @Override
Jeff Sharkey88ded902013-10-25 14:53:06 -0700202 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 }
Hung-ying Tyan3e722ca2009-10-15 16:11:39 +0800215 }
216}