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.Intent;
20import android.net.Uri;
21import android.os.Bundle;
22import android.preference.PreferenceActivity;
23import android.provider.DocumentsContract;
24import android.security.Credentials;
25import android.security.KeyChain;
26import android.util.Log;
27import android.widget.Toast;
28
29import libcore.io.IoUtils;
30import libcore.io.Streams;
31
32import java.io.IOException;
33import java.io.InputStream;
34
35/**
36 * The main class for installing certificates to the system keystore. It reacts
37 * to the public {@link Credentials#INSTALL_ACTION} intent.
38 */
39public class CertInstallerMain extends PreferenceActivity {
40    private static final String TAG = "CertInstaller";
41
42    private static final int REQUEST_INSTALL = 1;
43    private static final int REQUEST_OPEN_DOCUMENT = 2;
44
45    private static final String[] ACCEPT_MIME_TYPES = {
46            "application/x-pkcs12",
47            "application/x-x509-ca-cert",
48            "application/x-x509-user-cert",
49            "application/x-x509-server-cert",
50            "application/x-pem-file",
51            "application/pkix-cert"
52    };
53
54    @Override
55    protected void onCreate(Bundle savedInstanceState) {
56        super.onCreate(savedInstanceState);
57
58        setResult(RESULT_CANCELED);
59
60        final Intent intent = getIntent();
61        final String action = intent.getAction();
62
63        if (Credentials.INSTALL_ACTION.equals(action)
64                || Credentials.INSTALL_AS_USER_ACTION.equals(action)) {
65            Bundle bundle = intent.getExtras();
66
67            /*
68             * There is a special INSTALL_AS_USER action that this activity is
69             * aliased to, but you have to have a permission to call it. If the
70             * caller got here any other way, remove the extra that we allow in
71             * that INSTALL_AS_USER path.
72             */
73            if (bundle != null && !Credentials.INSTALL_AS_USER_ACTION.equals(action)) {
74                bundle.remove(Credentials.EXTRA_INSTALL_AS_UID);
75            }
76
77            // If bundle is empty of any actual credentials, ask user to open.
78            // Otherwise, pass extras to CertInstaller to install those credentials.
79            // Either way, we use KeyChain.EXTRA_NAME as the default name if available.
80            if (bundle == null
81                    || bundle.isEmpty()
82                    || (bundle.size() == 1
83                        && (bundle.containsKey(KeyChain.EXTRA_NAME)
84                            || bundle.containsKey(Credentials.EXTRA_INSTALL_AS_UID)))) {
85                final Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
86                openIntent.setType("*/*");
87                openIntent.putExtra(Intent.EXTRA_MIME_TYPES, ACCEPT_MIME_TYPES);
88                openIntent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
89                startActivityForResult(openIntent, REQUEST_OPEN_DOCUMENT);
90            } else {
91                final Intent installIntent = new Intent(this, CertInstaller.class);
92                installIntent.putExtras(intent);
93                startActivityForResult(installIntent, REQUEST_INSTALL);
94            }
95        } else if (Intent.ACTION_VIEW.equals(action)) {
96            startInstallActivity(intent.getType(), intent.getData());
97        }
98    }
99
100    private void startInstallActivity(String mimeType, Uri uri) {
101        if (mimeType == null) {
102            mimeType = getContentResolver().getType(uri);
103        }
104
105        InputStream in = null;
106        try {
107            in = getContentResolver().openInputStream(uri);
108
109            final byte[] raw = Streams.readFully(in);
110            startInstallActivity(mimeType, raw);
111
112        } catch (IOException e) {
113            Log.e(TAG, "Failed to read certificate: " + e);
114            Toast.makeText(this, R.string.cert_read_error, Toast.LENGTH_LONG).show();
115        } finally {
116            IoUtils.closeQuietly(in);
117        }
118    }
119
120    private void startInstallActivity(String mimeType, byte[] value) {
121        Intent intent = new Intent(this, CertInstaller.class);
122        if ("application/x-pkcs12".equals(mimeType)) {
123            intent.putExtra(KeyChain.EXTRA_PKCS12, value);
124        } else if ("application/x-x509-ca-cert".equals(mimeType)
125                || "application/x-x509-user-cert".equals(mimeType)
126                || "application/x-x509-server-cert".equals(mimeType)
127                || "application/x-pem-file".equals(mimeType)
128                || "application/pkix-cert".equals(mimeType)) {
129            intent.putExtra(KeyChain.EXTRA_CERTIFICATE, value);
130        } else {
131            throw new IllegalArgumentException("Unknown MIME type: " + mimeType);
132        }
133
134        startActivityForResult(intent, REQUEST_INSTALL);
135    }
136
137    @Override
138    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
139        if (requestCode == REQUEST_OPEN_DOCUMENT) {
140            if (resultCode == RESULT_OK) {
141                startInstallActivity(null, data.getData());
142            } else {
143                finish();
144            }
145        } else if (requestCode == REQUEST_INSTALL) {
146            setResult(resultCode);
147            finish();
148        } else {
149            Log.w(TAG, "unknown request code: " + requestCode);
150        }
151    }
152}
153