1/*
2 * Copyright (C) 2011 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 */
16package android.security;
17
18import android.app.Activity;
19import android.app.PendingIntent;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.os.IBinder;
25import android.os.Looper;
26import android.os.RemoteException;
27import java.io.ByteArrayInputStream;
28import java.io.Closeable;
29import java.security.InvalidKeyException;
30import java.security.Principal;
31import java.security.PrivateKey;
32import java.security.cert.Certificate;
33import java.security.cert.CertificateException;
34import java.security.cert.CertificateFactory;
35import java.security.cert.X509Certificate;
36import java.util.List;
37import java.util.concurrent.BlockingQueue;
38import java.util.concurrent.LinkedBlockingQueue;
39
40import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine;
41import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
42
43/**
44 * The {@code KeyChain} class provides access to private keys and
45 * their corresponding certificate chains in credential storage.
46 *
47 * <p>Applications accessing the {@code KeyChain} normally go through
48 * these steps:
49 *
50 * <ol>
51 *
52 * <li>Receive a callback from an {@link javax.net.ssl.X509KeyManager
53 * X509KeyManager} that a private key is requested.
54 *
55 * <li>Call {@link #choosePrivateKeyAlias
56 * choosePrivateKeyAlias} to allow the user to select from a
57 * list of currently available private keys and corresponding
58 * certificate chains. The chosen alias will be returned by the
59 * callback {@link KeyChainAliasCallback#alias}, or null if no private
60 * key is available or the user cancels the request.
61 *
62 * <li>Call {@link #getPrivateKey} and {@link #getCertificateChain} to
63 * retrieve the credentials to return to the corresponding {@link
64 * javax.net.ssl.X509KeyManager} callbacks.
65 *
66 * </ol>
67 *
68 * <p>An application may remember the value of a selected alias to
69 * avoid prompting the user with {@link #choosePrivateKeyAlias
70 * choosePrivateKeyAlias} on subsequent connections. If the alias is
71 * no longer valid, null will be returned on lookups using that value
72 *
73 * <p>An application can request the installation of private keys and
74 * certificates via the {@code Intent} provided by {@link
75 * #createInstallIntent}. Private keys installed via this {@code
76 * Intent} will be accessible via {@link #choosePrivateKeyAlias} while
77 * Certificate Authority (CA) certificates will be trusted by all
78 * applications through the default {@code X509TrustManager}.
79 */
80// TODO reference intent for credential installation when public
81public final class KeyChain {
82
83    private static final String TAG = "KeyChain";
84
85    /**
86     * @hide Also used by KeyChainService implementation
87     */
88    public static final String ACCOUNT_TYPE = "com.android.keychain";
89
90    /**
91     * Action to bring up the KeyChainActivity
92     */
93    private static final String ACTION_CHOOSER = "com.android.keychain.CHOOSER";
94
95    /**
96     * Extra for use with {@link #ACTION_CHOOSER}
97     * @hide Also used by KeyChainActivity implementation
98     */
99    public static final String EXTRA_RESPONSE = "response";
100
101    /**
102     * Extra for use with {@link #ACTION_CHOOSER}
103     * @hide Also used by KeyChainActivity implementation
104     */
105    public static final String EXTRA_HOST = "host";
106
107    /**
108     * Extra for use with {@link #ACTION_CHOOSER}
109     * @hide Also used by KeyChainActivity implementation
110     */
111    public static final String EXTRA_PORT = "port";
112
113    /**
114     * Extra for use with {@link #ACTION_CHOOSER}
115     * @hide Also used by KeyChainActivity implementation
116     */
117    public static final String EXTRA_ALIAS = "alias";
118
119    /**
120     * Extra for use with {@link #ACTION_CHOOSER}
121     * @hide Also used by KeyChainActivity implementation
122     */
123    public static final String EXTRA_SENDER = "sender";
124
125    /**
126     * Action to bring up the CertInstaller.
127     */
128    private static final String ACTION_INSTALL = "android.credentials.INSTALL";
129
130    /**
131     * Optional extra to specify a {@code String} credential name on
132     * the {@code Intent} returned by {@link #createInstallIntent}.
133     */
134    // Compatible with old com.android.certinstaller.CredentialHelper.CERT_NAME_KEY
135    public static final String EXTRA_NAME = "name";
136
137    /**
138     * Optional extra to specify an X.509 certificate to install on
139     * the {@code Intent} returned by {@link #createInstallIntent}.
140     * The extra value should be a PEM or ASN.1 DER encoded {@code
141     * byte[]}. An {@link X509Certificate} can be converted to DER
142     * encoded bytes with {@link X509Certificate#getEncoded}.
143     *
144     * <p>{@link #EXTRA_NAME} may be used to provide a default alias
145     * name for the installed certificate.
146     */
147    // Compatible with old android.security.Credentials.CERTIFICATE
148    public static final String EXTRA_CERTIFICATE = "CERT";
149
150    /**
151     * Optional extra for use with the {@code Intent} returned by
152     * {@link #createInstallIntent} to specify a PKCS#12 key store to
153     * install. The extra value should be a {@code byte[]}. The bytes
154     * may come from an external source or be generated with {@link
155     * java.security.KeyStore#store} on a "PKCS12" instance.
156     *
157     * <p>The user will be prompted for the password to load the key store.
158     *
159     * <p>The key store will be scanned for {@link
160     * java.security.KeyStore.PrivateKeyEntry} entries and both the
161     * private key and associated certificate chain will be installed.
162     *
163     * <p>{@link #EXTRA_NAME} may be used to provide a default alias
164     * name for the installed credentials.
165     */
166    // Compatible with old android.security.Credentials.PKCS12
167    public static final String EXTRA_PKCS12 = "PKCS12";
168
169
170    /**
171     * Broadcast Action: Indicates the trusted storage has changed. Sent when
172     * one of this happens:
173     *
174     * <ul>
175     * <li>a new CA is added,
176     * <li>an existing CA is removed or disabled,
177     * <li>a disabled CA is enabled,
178     * <li>trusted storage is reset (all user certs are cleared),
179     * <li>when permission to access a private key is changed.
180     * </ul>
181     */
182    public static final String ACTION_STORAGE_CHANGED = "android.security.STORAGE_CHANGED";
183
184    /**
185     * Returns an {@code Intent} that can be used for credential
186     * installation. The intent may be used without any extras, in
187     * which case the user will be able to install credentials from
188     * their own source.
189     *
190     * <p>Alternatively, {@link #EXTRA_CERTIFICATE} or {@link
191     * #EXTRA_PKCS12} maybe used to specify the bytes of an X.509
192     * certificate or a PKCS#12 key store for installation. These
193     * extras may be combined with {@link #EXTRA_NAME} to provide a
194     * default alias name for credentials being installed.
195     *
196     * <p>When used with {@link Activity#startActivityForResult},
197     * {@link Activity#RESULT_OK} will be returned if a credential was
198     * successfully installed, otherwise {@link
199     * Activity#RESULT_CANCELED} will be returned.
200     */
201    public static Intent createInstallIntent() {
202        Intent intent = new Intent(ACTION_INSTALL);
203        intent.setClassName("com.android.certinstaller",
204                            "com.android.certinstaller.CertInstallerMain");
205        return intent;
206    }
207
208    /**
209     * Launches an {@code Activity} for the user to select the alias
210     * for a private key and certificate pair for authentication. The
211     * selected alias or null will be returned via the
212     * KeyChainAliasCallback callback.
213     *
214     * <p>{@code keyTypes} and {@code issuers} may be used to
215     * highlight suggested choices to the user, although to cope with
216     * sometimes erroneous values provided by servers, the user may be
217     * able to override these suggestions.
218     *
219     * <p>{@code host} and {@code port} may be used to give the user
220     * more context about the server requesting the credentials.
221     *
222     * <p>{@code alias} allows the chooser to preselect an existing
223     * alias which will still be subject to user confirmation.
224     *
225     * @param activity The {@link Activity} context to use for
226     *     launching the new sub-Activity to prompt the user to select
227     *     a private key; used only to call startActivity(); must not
228     *     be null.
229     * @param response Callback to invoke when the request completes;
230     *     must not be null
231     * @param keyTypes The acceptable types of asymmetric keys such as
232     *     "RSA" or "DSA", or a null array.
233     * @param issuers The acceptable certificate issuers for the
234     *     certificate matching the private key, or null.
235     * @param host The host name of the server requesting the
236     *     certificate, or null if unavailable.
237     * @param port The port number of the server requesting the
238     *     certificate, or -1 if unavailable.
239     * @param alias The alias to preselect if available, or null if
240     *     unavailable.
241     */
242    public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response,
243                                             String[] keyTypes, Principal[] issuers,
244                                             String host, int port,
245                                             String alias) {
246        /*
247         * TODO currently keyTypes, issuers are unused. They are meant
248         * to follow the semantics and purpose of X509KeyManager
249         * method arguments.
250         *
251         * keyTypes would allow the list to be filtered and typically
252         * will be set correctly by the server. In practice today,
253         * most all users will want only RSA, rarely DSA, and usually
254         * only a small number of certs will be available.
255         *
256         * issuers is typically not useful. Some servers historically
257         * will send the entire list of public CAs known to the
258         * server. Others will send none. If this is used, if there
259         * are no matches after applying the constraint, it should be
260         * ignored.
261         */
262        if (activity == null) {
263            throw new NullPointerException("activity == null");
264        }
265        if (response == null) {
266            throw new NullPointerException("response == null");
267        }
268        Intent intent = new Intent(ACTION_CHOOSER);
269        intent.putExtra(EXTRA_RESPONSE, new AliasResponse(response));
270        intent.putExtra(EXTRA_HOST, host);
271        intent.putExtra(EXTRA_PORT, port);
272        intent.putExtra(EXTRA_ALIAS, alias);
273        // the PendingIntent is used to get calling package name
274        intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0, new Intent(), 0));
275        activity.startActivity(intent);
276    }
277
278    private static class AliasResponse extends IKeyChainAliasCallback.Stub {
279        private final KeyChainAliasCallback keyChainAliasResponse;
280        private AliasResponse(KeyChainAliasCallback keyChainAliasResponse) {
281            this.keyChainAliasResponse = keyChainAliasResponse;
282        }
283        @Override public void alias(String alias) {
284            keyChainAliasResponse.alias(alias);
285        }
286    }
287
288    /**
289     * Returns the {@code PrivateKey} for the requested alias, or null
290     * if no there is no result.
291     *
292     * @param alias The alias of the desired private key, typically
293     * returned via {@link KeyChainAliasCallback#alias}.
294     * @throws KeyChainException if the alias was valid but there was some problem accessing it.
295     */
296    public static PrivateKey getPrivateKey(Context context, String alias)
297            throws KeyChainException, InterruptedException {
298        if (alias == null) {
299            throw new NullPointerException("alias == null");
300        }
301        KeyChainConnection keyChainConnection = bind(context);
302        try {
303            final IKeyChainService keyChainService = keyChainConnection.getService();
304            final String keyId = keyChainService.requestPrivateKey(alias);
305            if (keyId == null) {
306                throw new KeyChainException("keystore had a problem");
307            }
308
309            final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
310            return engine.getPrivateKeyById(keyId);
311        } catch (RemoteException e) {
312            throw new KeyChainException(e);
313        } catch (RuntimeException e) {
314            // only certain RuntimeExceptions can be propagated across the IKeyChainService call
315            throw new KeyChainException(e);
316        } catch (InvalidKeyException e) {
317            throw new KeyChainException(e);
318        } finally {
319            keyChainConnection.close();
320        }
321    }
322
323    /**
324     * Returns the {@code X509Certificate} chain for the requested
325     * alias, or null if no there is no result.
326     *
327     * @param alias The alias of the desired certificate chain, typically
328     * returned via {@link KeyChainAliasCallback#alias}.
329     * @throws KeyChainException if the alias was valid but there was some problem accessing it.
330     */
331    public static X509Certificate[] getCertificateChain(Context context, String alias)
332            throws KeyChainException, InterruptedException {
333        if (alias == null) {
334            throw new NullPointerException("alias == null");
335        }
336        KeyChainConnection keyChainConnection = bind(context);
337        try {
338            IKeyChainService keyChainService = keyChainConnection.getService();
339            byte[] certificateBytes = keyChainService.getCertificate(alias);
340            TrustedCertificateStore store = new TrustedCertificateStore();
341            List<X509Certificate> chain = store
342                    .getCertificateChain(toCertificate(certificateBytes));
343            return chain.toArray(new X509Certificate[chain.size()]);
344        } catch (RemoteException e) {
345            throw new KeyChainException(e);
346        } catch (RuntimeException e) {
347            // only certain RuntimeExceptions can be propagated across the IKeyChainService call
348            throw new KeyChainException(e);
349        } finally {
350            keyChainConnection.close();
351        }
352    }
353
354    private static X509Certificate toCertificate(byte[] bytes) {
355        if (bytes == null) {
356            throw new IllegalArgumentException("bytes == null");
357        }
358        try {
359            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
360            Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
361            return (X509Certificate) cert;
362        } catch (CertificateException e) {
363            throw new AssertionError(e);
364        }
365    }
366
367    /**
368     * @hide for reuse by CertInstaller and Settings.
369     * @see KeyChain#bind
370     */
371    public final static class KeyChainConnection implements Closeable {
372        private final Context context;
373        private final ServiceConnection serviceConnection;
374        private final IKeyChainService service;
375        private KeyChainConnection(Context context,
376                                   ServiceConnection serviceConnection,
377                                   IKeyChainService service) {
378            this.context = context;
379            this.serviceConnection = serviceConnection;
380            this.service = service;
381        }
382        @Override public void close() {
383            context.unbindService(serviceConnection);
384        }
385        public IKeyChainService getService() {
386            return service;
387        }
388    }
389
390    /**
391     * @hide for reuse by CertInstaller and Settings.
392     *
393     * Caller should call unbindService on the result when finished.
394     */
395    public static KeyChainConnection bind(Context context) throws InterruptedException {
396        if (context == null) {
397            throw new NullPointerException("context == null");
398        }
399        ensureNotOnMainThread(context);
400        final BlockingQueue<IKeyChainService> q = new LinkedBlockingQueue<IKeyChainService>(1);
401        ServiceConnection keyChainServiceConnection = new ServiceConnection() {
402            volatile boolean mConnectedAtLeastOnce = false;
403            @Override public void onServiceConnected(ComponentName name, IBinder service) {
404                if (!mConnectedAtLeastOnce) {
405                    mConnectedAtLeastOnce = true;
406                    try {
407                        q.put(IKeyChainService.Stub.asInterface(service));
408                    } catch (InterruptedException e) {
409                        // will never happen, since the queue starts with one available slot
410                    }
411                }
412            }
413            @Override public void onServiceDisconnected(ComponentName name) {}
414        };
415        boolean isBound = context.bindService(new Intent(IKeyChainService.class.getName()),
416                                              keyChainServiceConnection,
417                                              Context.BIND_AUTO_CREATE);
418        if (!isBound) {
419            throw new AssertionError("could not bind to KeyChainService");
420        }
421        return new KeyChainConnection(context, keyChainServiceConnection, q.take());
422    }
423
424    private static void ensureNotOnMainThread(Context context) {
425        Looper looper = Looper.myLooper();
426        if (looper != null && looper == context.getMainLooper()) {
427            throw new IllegalStateException(
428                    "calling this from your main thread can lead to deadlock");
429        }
430    }
431}
432