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
17package com.android.certinstaller;
18
19import android.content.Context;
20import android.content.Intent;
21import android.net.Uri;
22import android.os.Bundle;
23import android.os.UserManager;
24import android.preference.PreferenceActivity;
25import android.provider.DocumentsContract;
26import android.security.Credentials;
27import android.security.KeyChain;
28import android.util.Log;
29import android.widget.Toast;
30
31import java.io.BufferedInputStream;
32import java.io.IOException;
33import java.io.InputStream;
34import java.util.HashMap;
35import java.util.Map;
36
37import libcore.io.IoUtils;
38import libcore.io.Streams;
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 */
44public 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    private void startInstallActivity(String mimeType, Uri uri) {
124        if (mimeType == null) {
125            mimeType = getContentResolver().getType(uri);
126        }
127
128        String target = MIME_MAPPINGS.get(mimeType);
129        if (target == null) {
130            throw new IllegalArgumentException("Unknown MIME type: " + mimeType);
131        }
132
133        if (WIFI_CONFIG.equals(target)) {
134            startWifiInstallActivity(mimeType, uri);
135        }
136        else {
137            InputStream in = null;
138            try {
139                in = getContentResolver().openInputStream(uri);
140
141                final byte[] raw = Streams.readFully(in);
142                startInstallActivity(target, raw);
143
144            } catch (IOException e) {
145                Log.e(TAG, "Failed to read certificate: " + e);
146                Toast.makeText(this, R.string.cert_read_error, Toast.LENGTH_LONG).show();
147            } finally {
148                IoUtils.closeQuietly(in);
149            }
150        }
151    }
152
153    private void startInstallActivity(String target, byte[] value) {
154        Intent intent = new Intent(this, CertInstaller.class);
155        intent.putExtra(target, value);
156
157        startActivityForResult(intent, REQUEST_INSTALL);
158    }
159
160    private void startWifiInstallActivity(String mimeType, Uri uri) {
161        Intent intent = new Intent(this, WiFiInstaller.class);
162        try (BufferedInputStream in =
163                     new BufferedInputStream(getContentResolver().openInputStream(uri))) {
164            byte[] data = Streams.readFully(in);
165            intent.putExtra(WIFI_CONFIG_FILE, uri.toString());
166            intent.putExtra(WIFI_CONFIG_DATA, data);
167            intent.putExtra(WIFI_CONFIG, mimeType);
168            startActivityForResult(intent, REQUEST_INSTALL);
169        } catch (IOException e) {
170            Log.e(TAG, "Failed to read wifi config: " + e);
171            Toast.makeText(this, R.string.cert_read_error, Toast.LENGTH_LONG).show();
172        }
173    }
174
175    @Override
176    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
177        if (requestCode == REQUEST_OPEN_DOCUMENT) {
178            if (resultCode == RESULT_OK) {
179                startInstallActivity(null, data.getData());
180            } else {
181                finish();
182            }
183        } else if (requestCode == REQUEST_INSTALL) {
184            setResult(resultCode);
185            finish();
186        } else {
187            Log.w(TAG, "unknown request code: " + requestCode);
188        }
189    }
190}
191